From bfb41d72735236e221be4c18a647d5e40cf23a92 Mon Sep 17 00:00:00 2001 From: "Takeshi Ikuma (LSUHSC)" Date: Thu, 20 Jun 2024 08:31:19 -0500 Subject: [PATCH 01/10] test update (non-reproducible) --- tests/test_integration.py | 36 ++++++++++++++++-- .../test_integration_autodoc_type_aliases.py | 38 ++++++++++++++++++- 2 files changed, 69 insertions(+), 5 deletions(-) diff --git a/tests/test_integration.py b/tests/test_integration.py index 8d732d9..0b63691 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -1270,7 +1270,9 @@ def typehints_use_signature(a: AsyncGenerator) -> AsyncGenerator: prolog = """ .. |test_node_start| replace:: {test_node_start} -""".format(test_node_start="test_start") +""".format( + test_node_start="test_start" +) @expected( @@ -1307,7 +1309,9 @@ def docstring_with_multiline_note_after_params_prolog_replace(param: int) -> Non epilog = """ .. |test_node_end| replace:: {test_node_end} -""".format(test_node_end="test_end") +""".format( + test_node_end="test_end" +) @expected( @@ -1357,6 +1361,32 @@ def docstring_with_multiline_note_after_params_epilog_replace(param: int) -> Non } +@expected( + """ +mod.function1(x) + + Function docstring. + + Parameters: + **x** (Input) -- foo + + Return type: + Output + + Returns: + something + +""", +) +def function1(x: "Input") -> "Output": + """ + Function docstring. + + :param x: foo + :return: something + """ + + @pytest.mark.parametrize("val", [x for x in globals().values() if hasattr(x, "EXPECTED")]) @pytest.mark.parametrize("conf_run", ["default_conf", "prolog_conf", "epilog_conf", "bothlog_conf"]) @pytest.mark.sphinx("text", testroot="integration") @@ -1382,7 +1412,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: Cannot resolve forward reference in type annotations of ", value): assert not value result = (Path(app.srcdir) / "_build/text/index.txt").read_text() diff --git a/tests/test_integration_autodoc_type_aliases.py b/tests/test_integration_autodoc_type_aliases.py index 8e88507..6d82f3f 100644 --- a/tests/test_integration_autodoc_type_aliases.py +++ b/tests/test_integration_autodoc_type_aliases.py @@ -63,6 +63,29 @@ def function(x: ArrayLike) -> str: # noqa: ARG001 """ +class Schema: ... + + +class _SchemaMeta(type): + def __new__(cls, name, bases, dct) -> "_SchemaMeta": + return Schema + + +class Schema(metaclass=_SchemaMeta): ... + +@expected( + """ +mod.Foo(schema) + +""", +) +def do_something(self, schema: Schema) -> None: + """ + Args: + schema: Some schema. + """ + + # Config settings for each test run. # Config Name: Sphinx Options as Dict. configs = { @@ -72,7 +95,18 @@ def function(x: ArrayLike) -> str: # noqa: ARG001 } } } - +# typehints_use_signature +# typehints_defaults +# typehints_fully_qualified = False +# always_document_param_types = False +# always_use_bars_union = False +# typehints_document_rtype = True +# typehints_use_rtype = True +# typehints_defaults = "comma" +# simplify_optional_unions = True +# typehints_formatter = None +# typehints_use_signature = True +# typehints_use_signature_return = True @pytest.mark.parametrize("val", [x for x in globals().values() if hasattr(x, "EXPECTED")]) @pytest.mark.parametrize("conf_run", list(configs.keys())) @@ -94,7 +128,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() From 2dee537fcc97d5677e786c19076d392e964ce193 Mon Sep 17 00:00:00 2001 From: "Takeshi Ikuma (LSUHSC)" Date: Thu, 20 Jun 2024 09:27:01 -0500 Subject: [PATCH 02/10] fix candidate #1 --- src/sphinx_autodoc_typehints/__init__.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/sphinx_autodoc_typehints/__init__.py b/src/sphinx_autodoc_typehints/__init__.py index 0c02ad9..f778d65 100644 --- a/src/sphinx_autodoc_typehints/__init__.py +++ b/src/sphinx_autodoc_typehints/__init__.py @@ -718,7 +718,11 @@ def _inject_signature( # noqa: C901 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) + annotation = ( + ForwardRef(arg_type.annotation, is_argument=True, is_class=False) + if arg_type.annotation in type_aliases + else type_hints.get(arg_name) + ) default = signature.parameters[arg_name].default From 58942728e8682432d8b375642eb50d7fef613271 Mon Sep 17 00:00:00 2001 From: "Takeshi Ikuma (LSUHSC)" Date: Thu, 20 Jun 2024 10:24:22 -0500 Subject: [PATCH 03/10] fixes issue #462 --- src/sphinx_autodoc_typehints/__init__.py | 2 +- .../test_integration_autodoc_type_aliases.py | 70 ++++++++++++------- 2 files changed, 44 insertions(+), 28 deletions(-) diff --git a/src/sphinx_autodoc_typehints/__init__.py b/src/sphinx_autodoc_typehints/__init__.py index f778d65..8a9de63 100644 --- a/src/sphinx_autodoc_typehints/__init__.py +++ b/src/sphinx_autodoc_typehints/__init__.py @@ -720,7 +720,7 @@ def _inject_signature( # noqa: C901 for arg_name, arg_type in signature.parameters.items(): annotation = ( ForwardRef(arg_type.annotation, is_argument=True, is_class=False) - if arg_type.annotation in type_aliases + if str(arg_type.annotation) in type_aliases else type_hints.get(arg_name) ) diff --git a/tests/test_integration_autodoc_type_aliases.py b/tests/test_integration_autodoc_type_aliases.py index 6d82f3f..9094465 100644 --- a/tests/test_integration_autodoc_type_aliases.py +++ b/tests/test_integration_autodoc_type_aliases.py @@ -34,57 +34,72 @@ def dec(val: T) -> T: return dec -ArrayLike = Literal["test"] +from numpy.typing import ArrayLike + +# ArrayLike = Literal["test"] + + +class _SchemaMeta(type): + def __eq__(cls, other: Any) -> bool: + return True + + +class Schema(metaclass=_SchemaMeta): + pass @expected( - """\ -mod.function(x) + """ +mod.f(s) - Function docstring. + Do something. Parameters: - **x** (ArrayLike) -- foo - - Returns: - something + **s** ("Schema") -- Some schema. Return type: - bytes -""", + "Schema" +""" ) -def function(x: ArrayLike) -> str: # noqa: ARG001 +def f(s: Schema) -> Schema: """ - Function docstring. + Do something. - :param x: foo - :return: something - :rtype: bytes + Args: + s: Some schema. """ + return s -class Schema: ... +@expected( + """\ +mod.function(x, y) + Function docstring. -class _SchemaMeta(type): - def __new__(cls, name, bases, dct) -> "_SchemaMeta": - return Schema + Parameters: + * **x** (ArrayLike) -- foo + * **y** ("Schema") -- boo -class Schema(metaclass=_SchemaMeta): ... + Returns: + something -@expected( - """ -mod.Foo(schema) + Return type: + bytes """, ) -def do_something(self, schema: Schema) -> None: +def function(x: ArrayLike, y: Schema) -> str: # noqa: ARG001 """ - Args: - schema: Some schema. + Function docstring. + + :param x: foo + :param y: boo + :return: something + :rtype: bytes """ - + # Config settings for each test run. # Config Name: Sphinx Options as Dict. @@ -108,6 +123,7 @@ def do_something(self, schema: Schema) -> None: # typehints_use_signature = True # typehints_use_signature_return = True + @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") From aba77c99b2785e6dff5e5fc37365e386b50f68a9 Mon Sep 17 00:00:00 2001 From: "Takeshi Ikuma (LSUHSC)" Date: Thu, 20 Jun 2024 20:34:15 -0500 Subject: [PATCH 04/10] Fix annotation check exception (#462) Fix processing type aliases --- src/sphinx_autodoc_typehints/__init__.py | 32 +++++++----- .../test_integration_autodoc_type_aliases.py | 52 +++++++++++-------- 2 files changed, 49 insertions(+), 35 deletions(-) diff --git a/src/sphinx_autodoc_typehints/__init__.py b/src/sphinx_autodoc_typehints/__init__.py index 8a9de63..5756966 100644 --- a/src/sphinx_autodoc_typehints/__init__.py +++ b/src/sphinx_autodoc_typehints/__init__.py @@ -20,6 +20,9 @@ from sphinx.util.inspect import signature as sphinx_signature from sphinx.util.inspect import stringify_signature +from typing import get_type_hints +from sphinx.util.inspect import TypeAliasNamespace, TypeAliasForwardRef + from .parser import parse from .patches import install_patches from .version import __version__ @@ -194,6 +197,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 +410,11 @@ 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 +422,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 +483,12 @@ 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: TypeAliasForwardRef +) -> 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 +656,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,14 +728,9 @@ 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 = ( - ForwardRef(arg_type.annotation, is_argument=True, is_class=False) - if str(arg_type.annotation) in type_aliases - else type_hints.get(arg_name) - ) + 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 9094465..cb91cfc 100644 --- a/tests/test_integration_autodoc_type_aliases.py +++ b/tests/test_integration_autodoc_type_aliases.py @@ -34,9 +34,7 @@ def dec(val: T) -> T: return dec -from numpy.typing import ArrayLike - -# ArrayLike = Literal["test"] +ArrayLike = Literal["test"] class _SchemaMeta(type): @@ -71,6 +69,32 @@ def f(s: Schema) -> 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, y) @@ -78,7 +102,7 @@ def f(s: Schema) -> Schema: Function docstring. Parameters: - * **x** (ArrayLike) -- foo + * **x** (Array) -- foo * **y** ("Schema") -- boo @@ -103,25 +127,7 @@ def function(x: ArrayLike, y: Schema) -> str: # noqa: ARG001 # Config settings for each test run. # Config Name: Sphinx Options as Dict. -configs = { - "default_conf": { - "autodoc_type_aliases": { - "ArrayLike": "ArrayLike", - } - } -} -# typehints_use_signature -# typehints_defaults -# typehints_fully_qualified = False -# always_document_param_types = False -# always_use_bars_union = False -# typehints_document_rtype = True -# typehints_use_rtype = True -# typehints_defaults = "comma" -# simplify_optional_unions = True -# typehints_formatter = None -# typehints_use_signature = True -# typehints_use_signature_return = True +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")]) From e858479286546904690bc04387995976629a34f2 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 21 Jun 2024 01:40:21 +0000 Subject: [PATCH 05/10] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/sphinx_autodoc_typehints/__init__.py | 9 ++------- tests/test_integration.py | 16 ++++++---------- tests/test_integration_autodoc_type_aliases.py | 2 +- 3 files changed, 9 insertions(+), 18 deletions(-) diff --git a/src/sphinx_autodoc_typehints/__init__.py b/src/sphinx_autodoc_typehints/__init__.py index 5756966..c0416f3 100644 --- a/src/sphinx_autodoc_typehints/__init__.py +++ b/src/sphinx_autodoc_typehints/__init__.py @@ -17,11 +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 typing import get_type_hints -from sphinx.util.inspect import TypeAliasNamespace, TypeAliasForwardRef from .parser import parse from .patches import install_patches @@ -413,7 +410,6 @@ def _future_annotations_imported(obj: Any) -> bool: 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) @@ -728,8 +724,7 @@ def _inject_signature( # noqa: C901 app: Sphinx, lines: list[str], ) -> None: - - for arg_name, arg_type in signature.parameters.items(): + for arg_name in signature.parameters: annotation = type_hints.get(arg_name) default = signature.parameters[arg_name].default diff --git a/tests/test_integration.py b/tests/test_integration.py index 0b63691..a8577c4 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -1270,9 +1270,7 @@ def typehints_use_signature(a: AsyncGenerator) -> AsyncGenerator: prolog = """ .. |test_node_start| replace:: {test_node_start} -""".format( - test_node_start="test_start" -) +""".format(test_node_start="test_start") @expected( @@ -1309,9 +1307,7 @@ def docstring_with_multiline_note_after_params_prolog_replace(param: int) -> Non epilog = """ .. |test_node_end| replace:: {test_node_end} -""".format( - test_node_end="test_end" -) +""".format(test_node_end="test_end") @expected( @@ -1366,19 +1362,19 @@ def docstring_with_multiline_note_after_params_epilog_replace(param: int) -> Non mod.function1(x) Function docstring. - + Parameters: **x** (Input) -- foo - + Return type: Output - + Returns: something """, ) -def function1(x: "Input") -> "Output": +def function1(x: Input) -> Output: """ Function docstring. diff --git a/tests/test_integration_autodoc_type_aliases.py b/tests/test_integration_autodoc_type_aliases.py index cb91cfc..3de16d8 100644 --- a/tests/test_integration_autodoc_type_aliases.py +++ b/tests/test_integration_autodoc_type_aliases.py @@ -38,7 +38,7 @@ def dec(val: T) -> T: class _SchemaMeta(type): - def __eq__(cls, other: Any) -> bool: + def __eq__(cls, other: object) -> bool: return True From 68ff536352dd7e15f9440c8379353fb78dedf268 Mon Sep 17 00:00:00 2001 From: "Takeshi Ikuma (LSUHSC)" Date: Thu, 20 Jun 2024 20:47:32 -0500 Subject: [PATCH 06/10] reverted test_integration.py --- tests/test_integration.py | 28 +--------------------------- 1 file changed, 1 insertion(+), 27 deletions(-) diff --git a/tests/test_integration.py b/tests/test_integration.py index a8577c4..8d732d9 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -1357,32 +1357,6 @@ def docstring_with_multiline_note_after_params_epilog_replace(param: int) -> Non } -@expected( - """ -mod.function1(x) - - Function docstring. - - Parameters: - **x** (Input) -- foo - - Return type: - Output - - Returns: - something - -""", -) -def function1(x: Input) -> Output: - """ - Function docstring. - - :param x: foo - :return: something - """ - - @pytest.mark.parametrize("val", [x for x in globals().values() if hasattr(x, "EXPECTED")]) @pytest.mark.parametrize("conf_run", ["default_conf", "prolog_conf", "epilog_conf", "bothlog_conf"]) @pytest.mark.sphinx("text", testroot="integration") @@ -1408,7 +1382,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 - elif not re.search("WARNING: Cannot resolve forward reference in type annotations of ", value): + else: assert not value result = (Path(app.srcdir) / "_build/text/index.txt").read_text() From 84094cce1659bc465324c8441fbd3cacba77c54a Mon Sep 17 00:00:00 2001 From: "Takeshi Ikuma (LSUHSC)" Date: Thu, 20 Jun 2024 20:52:49 -0500 Subject: [PATCH 07/10] make _SchemaMeta hashable for ruff pre-commit test --- tests/test_integration_autodoc_type_aliases.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_integration_autodoc_type_aliases.py b/tests/test_integration_autodoc_type_aliases.py index 3de16d8..c76db15 100644 --- a/tests/test_integration_autodoc_type_aliases.py +++ b/tests/test_integration_autodoc_type_aliases.py @@ -38,6 +38,7 @@ def dec(val: T) -> T: class _SchemaMeta(type): + def __hash__(cls): ... def __eq__(cls, other: object) -> bool: return True From 250c73a71fa1907823d7079e0c25017714549177 Mon Sep 17 00:00:00 2001 From: "Takeshi Ikuma (LSUHSC)" Date: Thu, 20 Jun 2024 20:56:12 -0500 Subject: [PATCH 08/10] pre-commit ci fix try no 2 --- tests/test_integration_autodoc_type_aliases.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/test_integration_autodoc_type_aliases.py b/tests/test_integration_autodoc_type_aliases.py index c76db15..0e0ef03 100644 --- a/tests/test_integration_autodoc_type_aliases.py +++ b/tests/test_integration_autodoc_type_aliases.py @@ -37,8 +37,7 @@ def dec(val: T) -> T: ArrayLike = Literal["test"] -class _SchemaMeta(type): - def __hash__(cls): ... +class _SchemaMeta(type): # noqa: PLW1641 def __eq__(cls, other: object) -> bool: return True From 5c5827354629411e2fd5eee4b4afddc869bbef4c Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 21 Jun 2024 01:56:26 +0000 Subject: [PATCH 09/10] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- tests/test_integration_autodoc_type_aliases.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_integration_autodoc_type_aliases.py b/tests/test_integration_autodoc_type_aliases.py index 0e0ef03..7d21971 100644 --- a/tests/test_integration_autodoc_type_aliases.py +++ b/tests/test_integration_autodoc_type_aliases.py @@ -37,7 +37,7 @@ def dec(val: T) -> T: ArrayLike = Literal["test"] -class _SchemaMeta(type): # noqa: PLW1641 +class _SchemaMeta(type): # noqa: PLW1641 def __eq__(cls, other: object) -> bool: return True From 30dcfe1fbff940ff563ba0f249c8ff5888d7cebb Mon Sep 17 00:00:00 2001 From: "Takeshi Ikuma (LSUHSC)" Date: Thu, 20 Jun 2024 21:00:11 -0500 Subject: [PATCH 10/10] _get_type_hint(): fixed the type hint of localns arg --- src/sphinx_autodoc_typehints/__init__.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/sphinx_autodoc_typehints/__init__.py b/src/sphinx_autodoc_typehints/__init__.py index c0416f3..837598c 100644 --- a/src/sphinx_autodoc_typehints/__init__.py +++ b/src/sphinx_autodoc_typehints/__init__.py @@ -479,9 +479,7 @@ 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, localns: TypeAliasForwardRef -) -> 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, None, localns)