Skip to content

Commit

Permalink
Fix roles for types module (#516)
Browse files Browse the repository at this point in the history
Fixes #498

Also apparently the emitted role for FunctionType used to be wrong, so fixes that as well.
  • Loading branch information
flying-sheep authored Jan 16, 2025
1 parent 3af8fb6 commit aceb328
Show file tree
Hide file tree
Showing 2 changed files with 25 additions and 5 deletions.
20 changes: 18 additions & 2 deletions src/sphinx_autodoc_typehints/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,23 @@
from sphinx.ext.autodoc import Options

_LOGGER = logging.getLogger(__name__)
_PYDATA_ANNOTATIONS = {"Any", "AnyStr", "Callable", "ClassVar", "Literal", "NoReturn", "Optional", "Tuple", "Union"}
_PYDATA_ANNOTS_TYPING = {"Any", "AnyStr", "Callable", "ClassVar", "Literal", "NoReturn", "Optional", "Tuple", "Union"}
_PYDATA_ANNOTS_TYPES = {
*("AsyncGeneratorType", "BuiltinFunctionType", "BuiltinMethodType"),
*("CellType", "ClassMethodDescriptorType", "CoroutineType"),
"EllipsisType",
*("FrameType", "FunctionType"),
*("GeneratorType", "GetSetDescriptorType"),
"LambdaType",
*("MemberDescriptorType", "MethodDescriptorType", "MethodType", "MethodWrapperType"),
# NoneType is special, but included here for completeness' sake
*("NoneType", "NotImplementedType"),
"WrapperDescriptorType",
}
_PYDATA_ANNOTATIONS = {
*(("typing", n) for n in _PYDATA_ANNOTS_TYPING),
*(("types", n) for n in _PYDATA_ANNOTS_TYPES),
}

# types has a bunch of things like ModuleType where ModuleType.__module__ is
# "builtins" and ModuleType.__name__ is "module", so we have to check for this.
Expand Down Expand Up @@ -219,7 +235,7 @@ def format_annotation(annotation: Any, config: Config) -> str: # noqa: C901, PL
full_name = f"{module}.{class_name}" if module != "builtins" else class_name
fully_qualified: bool = getattr(config, "typehints_fully_qualified", False)
prefix = "" if fully_qualified or full_name == class_name else "~"
role = "data" if module == "typing" and class_name in _PYDATA_ANNOTATIONS else "class"
role = "data" if (module, class_name) in _PYDATA_ANNOTATIONS else "class"
args_format = "\\[{}]"
formatted_args: str | None = ""

Expand Down
10 changes: 7 additions & 3 deletions tests/test_sphinx_autodoc_typehints.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from io import StringIO
from pathlib import Path
from textwrap import dedent, indent
from types import FunctionType, ModuleType
from types import EllipsisType, FrameType, FunctionType, ModuleType, NotImplementedType, TracebackType
from typing import ( # noqa: UP035
IO,
Any,
Expand Down Expand Up @@ -168,8 +168,12 @@ def test_parse_annotation(annotation: Any, module: str, class_name: str, args: t
pytest.param(str, ":py:class:`str`", id="str"),
pytest.param(int, ":py:class:`int`", id="int"),
pytest.param(StringIO, ":py:class:`~io.StringIO`", id="StringIO"),
pytest.param(FunctionType, ":py:class:`~types.FunctionType`", id="FunctionType"),
pytest.param(EllipsisType, ":py:data:`~types.EllipsisType`", id="EllipsisType"),
pytest.param(FunctionType, ":py:data:`~types.FunctionType`", id="FunctionType"),
pytest.param(FrameType, ":py:data:`~types.FrameType`", id="FrameType"),
pytest.param(ModuleType, ":py:class:`~types.ModuleType`", id="ModuleType"),
pytest.param(NotImplementedType, ":py:data:`~types.NotImplementedType`", id="NotImplementedType"),
pytest.param(TracebackType, ":py:class:`~types.TracebackType`", id="TracebackType"),
pytest.param(type(None), ":py:obj:`None`", id="type None"),
pytest.param(type, ":py:class:`type`", id="type"),
pytest.param(Callable, ":py:class:`~collections.abc.Callable`", id="abc-Callable"),
Expand Down Expand Up @@ -414,7 +418,7 @@ def test_format_annotation(inv: Inventory, annotation: Any, expected_result: str
assert format_annotation(annotation, conf) == expected_result

# Test for the correct role (class vs data) using the official Sphinx inventory
if "typing" in expected_result:
if any(modname in expected_result for modname in ("typing", "types")):
m = re.match(r"^:py:(?P<role>class|data|func):`~(?P<name>[^`]+)`", result)
assert m, "No match"
name = m.group("name")
Expand Down

0 comments on commit aceb328

Please sign in to comment.