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

Fix issue #462 and another alias processing bug #463

Merged
merged 11 commits into from
Jun 21, 2024
27 changes: 16 additions & 11 deletions src/sphinx_autodoc_typehints/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Comment on lines +197 to +198
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we check whether we can make an xref here? Perhaps in a followup.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sorry, I'm not following. Could you elaborate?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wait I messed up the review process. I didn't mean to dismiss your review. my bad


try:
module = get_annotation_module(annotation)
class_name = get_annotation_class_name(annotation, module)
Expand Down Expand Up @@ -404,16 +407,18 @@ 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:
obj.__annotations__ = result
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


Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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

Expand Down
78 changes: 67 additions & 11 deletions tests/test_integration_autodoc_type_aliases.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,41 +37,97 @@ 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
"""


# 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")])
Expand All @@ -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()
Expand Down
Loading