Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 55 additions & 35 deletions src/sphinx_autodoc_typehints/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -808,43 +808,63 @@ def _inject_signature(
lines: list[str],
) -> None:
for arg_name in signature.parameters:
annotation = type_hints.get(arg_name)

default = signature.parameters[arg_name].default

if arg_name.endswith("_"):
arg_name = f"{arg_name[:-1]}\\_" # noqa: PLW2901

insert_index = None
for at, line in enumerate(lines):
if _line_is_param_line_for_arg(line, arg_name):
# Get the arg_name from the doc to match up for type in case it has a star prefix.
# Line is in the correct format so this is guaranteed to return tuple[str, str].
func = _get_sphinx_line_keyword_and_argument
_, arg_name = func(line) # type: ignore[assignment, misc] # noqa: PLW2901
insert_index = at
break

if annotation is not None and insert_index is None and app.config.always_document_param_types:
lines.append(f":param {arg_name}:")
insert_index = len(lines)

if insert_index is not None:
if annotation is None:
type_annotation = f":type {arg_name}: "
else:
short_literals = app.config.python_display_short_literal_types
formatted_annotation = add_type_css_class(
format_annotation(annotation, app.config, short_literals=short_literals)
)
type_annotation = f":type {arg_name}: {formatted_annotation}"
_inject_arg_signature(type_hints, signature, app, lines, arg_name)


def _inject_arg_signature(
type_hints: dict[str, Any],
signature: inspect.Signature,
app: Sphinx,
lines: list[str],
arg_name: str,
) -> None:
annotation = type_hints.get(arg_name)

default = signature.parameters[arg_name].default

if arg_name.endswith("_"):
arg_name = f"{arg_name[:-1]}\\_"

insert_index = None
for at, line in enumerate(lines):
if _line_is_param_line_for_arg(line, arg_name):
# Get the arg_name from the doc to match up for type in case it has a star prefix.
# Line is in the correct format so this is guaranteed to return tuple[str, str].
_, arg_name = _get_sphinx_line_keyword_and_argument(line) # type: ignore[assignment, misc]
insert_index = at
break

if annotation is not None and insert_index is None and app.config.always_document_param_types:
lines.append(f":param {arg_name}:")
insert_index = len(lines)

if insert_index is not None:
has_preexisting_annotation = False

if annotation is None:
type_annotation, has_preexisting_annotation = _find_preexisting_type_annotation(lines, arg_name)
else:
short_literals = app.config.python_display_short_literal_types
formatted_annotation = add_type_css_class(
format_annotation(annotation, app.config, short_literals=short_literals)
)
type_annotation = f":type {arg_name}: {formatted_annotation}"

if app.config.typehints_defaults:
formatted_default = format_default(app, default, annotation is not None or has_preexisting_annotation)
if formatted_default:
type_annotation = _append_default(app, lines, insert_index, type_annotation, formatted_default)

lines.insert(insert_index, type_annotation)

if app.config.typehints_defaults:
formatted_default = format_default(app, default, annotation is not None)
if formatted_default:
type_annotation = _append_default(app, lines, insert_index, type_annotation, formatted_default)

lines.insert(insert_index, type_annotation)
def _find_preexisting_type_annotation(lines: list[str], arg_name: str) -> tuple[str, bool]:
"""Find a type entry in the input docstring that matches the given arg name."""
type_annotation = f":type {arg_name}: "
for line in lines:
if line.startswith(type_annotation):
return line, True
return type_annotation, False


def _append_default(
Expand Down
11 changes: 11 additions & 0 deletions tests/roots/test-dummy/dummy_module_without_complete_typehints.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,14 @@ def function_with_defaults_and_some_typehints(x: int = 0, y=None) -> str: # noq
:param x: foo
:param y: bar
"""


def function_with_defaults_and_type_information_in_docstring(x, y=0) -> str: # noqa: ANN001
"""
Function docstring.

:type x: int
:type y: int
:param x: foo
:param y: bar
"""
1 change: 1 addition & 0 deletions tests/roots/test-dummy/without_complete_typehints.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ Simple Module
.. autofunction:: dummy_module_without_complete_typehints.function_with_some_defaults_and_some_typehints
.. autofunction:: dummy_module_without_complete_typehints.function_with_some_defaults_and_more_typehints
.. autofunction:: dummy_module_without_complete_typehints.function_with_defaults_and_some_typehints
.. autofunction:: dummy_module_without_complete_typehints.function_with_defaults_and_type_information_in_docstring
12 changes: 12 additions & 0 deletions tests/test_sphinx_autodoc_typehints.py
Original file line number Diff line number Diff line change
Expand Up @@ -1074,6 +1074,18 @@ def test_default_annotation_without_typehints(app: SphinxTestApp, status: String

Return type:
"str"

dummy_module_without_complete_typehints.function_with_defaults_and_type_information_in_docstring(x, y=0)

Function docstring.

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

* **y** (int, default: "0") -- bar

Return type:
"str"
"""
assert text_contents == dedent(expected_contents)

Expand Down