diff --git a/src/sphinx_autodoc_typehints/__init__.py b/src/sphinx_autodoc_typehints/__init__.py index ae679d0d..54ff42fe 100644 --- a/src/sphinx_autodoc_typehints/__init__.py +++ b/src/sphinx_autodoc_typehints/__init__.py @@ -475,19 +475,27 @@ def add(val: str) -> None: return result -def format_default(app: Sphinx, default: Any) -> str | None: +def format_default(app: Sphinx, default: Any, is_annotated: bool) -> str | None: if default is inspect.Parameter.empty: return None formatted = repr(default).replace("\\", "\\\\") - if app.config.typehints_defaults.startswith("braces"): - return f" (default: ``{formatted}``)" + + if is_annotated: + if app.config.typehints_defaults.startswith("braces"): + return f" (default: ``{formatted}``)" + else: # other option is comma + return f", default: ``{formatted}``" else: - return f", default: ``{formatted}``" + if app.config.typehints_defaults == "braces-after": + return f" (default: ``{formatted}``)" + else: + return f"default: ``{formatted}``" def process_docstring( app: Sphinx, what: str, name: str, obj: Any, options: Options | None, lines: list[str] # noqa: U100 ) -> None: + original_obj = obj obj = obj.fget if isinstance(obj, property) else obj if not callable(obj): @@ -561,41 +569,45 @@ def _inject_types_to_docstring( name: str, lines: list[str], ) -> None: - for arg_name, annotation in type_hints.items(): - if arg_name == "return": - continue # this is handled separately later - if signature is None or arg_name not in signature.parameters: - default = inspect.Parameter.empty - else: + + if signature is not None: + for arg_name in signature.parameters: + annotation = type_hints.get(arg_name, None) + default = signature.parameters[arg_name].default - if arg_name.endswith("_"): - arg_name = f"{arg_name[:-1]}\\_" - formatted_annotation = format_annotation(annotation, app.config) + 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 + 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 None and app.config.always_document_param_types: - lines.append(f":param {arg_name}:") - insert_index = len(lines) - - if insert_index is not None: - type_annotation = f":type {arg_name}: {formatted_annotation}" - if app.config.typehints_defaults: - formatted_default = format_default(app, default) - if formatted_default: - if app.config.typehints_defaults.endswith("after"): - lines[insert_index] += formatted_default - else: # add to last param doc line - type_annotation += formatted_default - lines.insert(insert_index, type_annotation) + if insert_index is not None: + if annotation is None: + type_annotation = f":type {arg_name}: " + else: + formatted_annotation = format_annotation(annotation, app.config) + type_annotation = f":type {arg_name}: {formatted_annotation}" + + if app.config.typehints_defaults: + formatted_default = format_default(app, default, annotation is not None) + if formatted_default: + if app.config.typehints_defaults.endswith("after"): + lines[insert_index] += formatted_default + else: # add to last param doc line + type_annotation += formatted_default + + lines.insert(insert_index, type_annotation) if "return" in type_hints and not inspect.isclass(original_obj): if what == "method" and name.endswith(".__init__"): # avoid adding a return type for data class __init__ diff --git a/tests/roots/test-dummy/dummy_module_without_complete_typehints.py b/tests/roots/test-dummy/dummy_module_without_complete_typehints.py new file mode 100644 index 00000000..6d7e91ef --- /dev/null +++ b/tests/roots/test-dummy/dummy_module_without_complete_typehints.py @@ -0,0 +1,34 @@ +def function_with_some_defaults_and_without_typehints(x, y=None): # noqa: U100 + """ + Function docstring. + + :param x: foo + :param y: bar + """ + + +def function_with_some_defaults_and_some_typehints(x: int, y=None): # noqa: U100 + """ + Function docstring. + + :param x: foo + :param y: bar + """ + + +def function_with_some_defaults_and_more_typehints(x: int, y=None) -> str: # noqa: U100 + """ + Function docstring. + + :param x: foo + :param y: bar + """ + + +def function_with_defaults_and_some_typehints(x: int = 0, y=None) -> str: # noqa: U100 + """ + Function docstring. + + :param x: foo + :param y: bar + """ diff --git a/tests/roots/test-dummy/without_complete_typehints.rst b/tests/roots/test-dummy/without_complete_typehints.rst new file mode 100644 index 00000000..2e7a204e --- /dev/null +++ b/tests/roots/test-dummy/without_complete_typehints.rst @@ -0,0 +1,7 @@ +Simple Module +============= + +.. autofunction:: dummy_module_without_complete_typehints.function_with_some_defaults_and_without_typehints +.. 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 diff --git a/tests/test_sphinx_autodoc_typehints.py b/tests/test_sphinx_autodoc_typehints.py index 8fd463bb..adcd40e1 100644 --- a/tests/test_sphinx_autodoc_typehints.py +++ b/tests/test_sphinx_autodoc_typehints.py @@ -1121,3 +1121,62 @@ def test_sphinx_output_with_use_signature_and_return(app: SphinxTestApp, status: "str" """ assert text_contents == dedent(expected_contents) + + +@pytest.mark.sphinx("text", testroot="dummy") +@patch("sphinx.writers.text.MAXWIDTH", 2000) +def test_default_annotation_without_typehints(app: SphinxTestApp, status: StringIO) -> None: + set_python_path() + app.config.master_doc = "without_complete_typehints" # type: ignore # create flag + app.config.typehints_defaults = "comma" # type: ignore + app.build() + assert "build succeeded" in status.getvalue() + text_path = pathlib.Path(app.srcdir) / "_build" / "text" / "without_complete_typehints.txt" + text_contents = text_path.read_text().replace("–", "--") + expected_contents = """\ + Simple Module + ************* + + dummy_module_without_complete_typehints.function_with_some_defaults_and_without_typehints(x, y=None) + + Function docstring. + + Parameters: + * **x** -- foo + + * **y** (default: "None") -- bar + + dummy_module_without_complete_typehints.function_with_some_defaults_and_some_typehints(x, y=None) + + Function docstring. + + Parameters: + * **x** ("int") -- foo + + * **y** (default: "None") -- bar + + dummy_module_without_complete_typehints.function_with_some_defaults_and_more_typehints(x, y=None) + + Function docstring. + + Parameters: + * **x** ("int") -- foo + + * **y** (default: "None") -- bar + + Return type: + "str" + + dummy_module_without_complete_typehints.function_with_defaults_and_some_typehints(x=0, y=None) + + Function docstring. + + Parameters: + * **x** ("int", default: "0") -- foo + + * **y** (default: "None") -- bar + + Return type: + "str" + """ + assert text_contents == dedent(expected_contents)