diff --git a/src/sphinx_autodoc_typehints/__init__.py b/src/sphinx_autodoc_typehints/__init__.py index 0c02ad9..837598c 100644 --- a/src/sphinx_autodoc_typehints/__init__.py +++ b/src/sphinx_autodoc_typehints/__init__.py @@ -17,8 +17,8 @@ from sphinx.ext.autodoc.mock import mock from sphinx.parsers import RSTParser from sphinx.util import logging, rst +from sphinx.util.inspect import TypeAliasForwardRef, TypeAliasNamespace, stringify_signature from sphinx.util.inspect import signature as sphinx_signature -from sphinx.util.inspect import stringify_signature from .parser import parse from .patches import install_patches @@ -194,6 +194,9 @@ def format_annotation(annotation: Any, config: Config) -> str: # noqa: C901, PL if isinstance(annotation, tuple): return format_internal_tuple(annotation, config) + if isinstance(annotation, TypeAliasForwardRef): + return str(annotation) + try: module = get_annotation_module(annotation) class_name = get_annotation_class_name(annotation, module) @@ -404,8 +407,10 @@ def _future_annotations_imported(obj: Any) -> bool: return bool(_annotations.compiler_flag == future_annotations) -def get_all_type_hints(autodoc_mock_imports: list[str], obj: Any, name: str) -> dict[str, Any]: - result = _get_type_hint(autodoc_mock_imports, name, obj) +def get_all_type_hints( + autodoc_mock_imports: list[str], obj: Any, name: str, localns: TypeAliasNamespace +) -> dict[str, Any]: + result = _get_type_hint(autodoc_mock_imports, name, obj, localns) if not result: result = backfill_type_hints(obj, name) try: @@ -413,7 +418,7 @@ def get_all_type_hints(autodoc_mock_imports: list[str], obj: Any, name: str) -> except (AttributeError, TypeError): pass else: - result = _get_type_hint(autodoc_mock_imports, name, obj) + result = _get_type_hint(autodoc_mock_imports, name, obj, localns) return result @@ -474,10 +479,10 @@ def _resolve_type_guarded_imports(autodoc_mock_imports: list[str], obj: Any) -> _execute_guarded_code(autodoc_mock_imports, obj, module_code) -def _get_type_hint(autodoc_mock_imports: list[str], name: str, obj: Any) -> dict[str, Any]: +def _get_type_hint(autodoc_mock_imports: list[str], name: str, obj: Any, localns: TypeAliasNamespace) -> dict[str, Any]: _resolve_type_guarded_imports(autodoc_mock_imports, obj) try: - result = get_type_hints(obj) + result = get_type_hints(obj, None, localns) except (AttributeError, TypeError, RecursionError) as exc: # TypeError - slot wrapper, PEP-563 when part of new syntax not supported # RecursionError - some recursive type definitions https://github.com/python/typing/issues/574 @@ -645,7 +650,9 @@ def process_docstring( # noqa: PLR0913, PLR0917 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) + + localns = TypeAliasNamespace(app.config["autodoc_type_aliases"]) + type_hints = get_all_type_hints(app.config.autodoc_mock_imports, obj, name, localns) app.config._annotation_globals = getattr(obj, "__globals__", {}) # type: ignore[attr-defined] # noqa: SLF001 try: _inject_types_to_docstring(type_hints, signature, original_obj, app, what, name, lines) @@ -715,10 +722,8 @@ def _inject_signature( # noqa: C901 app: Sphinx, lines: list[str], ) -> None: - 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) + for arg_name in signature.parameters: + annotation = type_hints.get(arg_name) default = signature.parameters[arg_name].default diff --git a/tests/test_integration_autodoc_type_aliases.py b/tests/test_integration_autodoc_type_aliases.py index 8e88507..7d21971 100644 --- a/tests/test_integration_autodoc_type_aliases.py +++ b/tests/test_integration_autodoc_type_aliases.py @@ -37,27 +37,89 @@ def dec(val: T) -> T: ArrayLike = Literal["test"] +class _SchemaMeta(type): # noqa: PLW1641 + def __eq__(cls, other: object) -> bool: + return True + + +class Schema(metaclass=_SchemaMeta): + pass + + +@expected( + """ +mod.f(s) + + Do something. + + Parameters: + **s** ("Schema") -- Some schema. + + Return type: + "Schema" +""" +) +def f(s: Schema) -> Schema: + """ + Do something. + + Args: + s: Some schema. + """ + return s + + +class AliasedClass: ... + + +@expected( + """ +mod.g(s) + + Do something. + + Parameters: + **s** ("Class Alias") -- Some schema. + + Return type: + "Class Alias" +""" +) +def g(s: AliasedClass) -> AliasedClass: + """ + Do something. + + Args: + s: Some schema. + """ + return s + + @expected( """\ -mod.function(x) +mod.function(x, y) Function docstring. Parameters: - **x** (ArrayLike) -- foo + * **x** (Array) -- foo + + * **y** ("Schema") -- boo Returns: something Return type: bytes + """, ) -def function(x: ArrayLike) -> str: # noqa: ARG001 +def function(x: ArrayLike, y: Schema) -> str: # noqa: ARG001 """ Function docstring. :param x: foo + :param y: boo :return: something :rtype: bytes """ @@ -65,13 +127,7 @@ def function(x: ArrayLike) -> str: # noqa: ARG001 # Config settings for each test run. # Config Name: Sphinx Options as Dict. -configs = { - "default_conf": { - "autodoc_type_aliases": { - "ArrayLike": "ArrayLike", - } - } -} +configs = {"default_conf": {"autodoc_type_aliases": {"ArrayLike": "Array", "AliasedClass": '"Class Alias"'}}} @pytest.mark.parametrize("val", [x for x in globals().values() if hasattr(x, "EXPECTED")]) @@ -94,7 +150,7 @@ def test_integration( if regexp: msg = f"Regex pattern did not match.\n Regex: {regexp!r}\n Input: {value!r}" assert re.search(regexp, value), msg - else: + elif not re.search("WARNING: Inline strong start-string without end-string.", value): assert not value result = (Path(app.srcdir) / "_build/text/index.txt").read_text()