diff --git a/src/sphinx_autodoc_typehints/__init__.py b/src/sphinx_autodoc_typehints/__init__.py index 4a2bbeef..94cd45e8 100644 --- a/src/sphinx_autodoc_typehints/__init__.py +++ b/src/sphinx_autodoc_typehints/__init__.py @@ -7,7 +7,6 @@ import types from ast import FunctionDef, Module, stmt from dataclasses import dataclass -from functools import lru_cache from typing import Any, AnyStr, Callable, ForwardRef, NewType, TypeVar, get_type_hints from docutils.frontend import OptionParser @@ -19,12 +18,11 @@ from sphinx.environment import BuildEnvironment from sphinx.ext.autodoc import Options from sphinx.ext.autodoc.mock import mock -from sphinx.ext.napoleon.docstring import GoogleDocstring from sphinx.util import logging from sphinx.util.inspect import signature as sphinx_signature from sphinx.util.inspect import stringify_signature -from .attributes_patch import patch_attribute_handling +from .patches import install_patches from .version import __version__ _LOGGER = logging.getLogger(__name__) @@ -786,82 +784,6 @@ def validate_config(app: Sphinx, env: BuildEnvironment, docnames: list[str]) -> raise ValueError(f"typehints_formatter needs to be callable or `None`, not {formatter}") -@lru_cache() # A cute way to make sure the function only runs once. -def fix_autodoc_typehints_for_overloaded_methods() -> None: - """ - sphinx-autodoc-typehints responds to the "autodoc-process-signature" event - to remove types from the signature line of functions. - - Normally, `FunctionDocumenter.format_signature` and - `MethodDocumenter.format_signature` call `super().format_signature` which - ends up going to `Documenter.format_signature`, and this last method emits - the `autodoc-process-signature` event. However, if there are overloads, - `FunctionDocumenter.format_signature` does something else and the event - never occurs. - - Here we remove this alternative code path by brute force. - - See https://github.com/tox-dev/sphinx-autodoc-typehints/issues/296 - """ - from sphinx.ext.autodoc import FunctionDocumenter, MethodDocumenter - - del FunctionDocumenter.format_signature - del MethodDocumenter.format_signature - - -def napoleon_numpy_docstring_return_type_processor( - app: Sphinx, what: str, name: str, obj: Any, options: Options | None, lines: list[str] # noqa: U100 -) -> None: - """Insert a : under Returns: to tell napoleon not to look for a return type.""" - if what not in ["function", "method"]: - return - if not getattr(app.config, "napoleon_numpy_docstring", False): - return - - # Search for the returns header: - # Returns: - # -------- - for idx, line in enumerate(lines[:-2]): - if line.lower().strip(":") not in ["return", "returns"]: - continue - # Underline detection. - chars = set(lines[idx + 1].strip()) - # Napoleon allows the underline to consist of a bunch of weirder things... - if len(chars) != 1 or list(chars)[0] not in "=-~_*+#": - continue - idx = idx + 2 - break - else: - return - - lines.insert(idx, ":") - - -def fix_napoleon_numpy_docstring_return_type(app: Sphinx) -> None: - """ - If no return type is explicitly provided, numpy docstrings will mess up and - use the return type text as return types. - """ - # standard priority is 500. Setting priority to 499 ensures this runs before - # napoleon's docstring processor. - app.connect("autodoc-process-docstring", napoleon_numpy_docstring_return_type_processor, priority=499) - - -def patched_lookup_annotation(*_args: Any) -> str: # noqa: U101 - """GoogleDocstring._lookup_annotation sometimes adds incorrect type - annotations to constructor parameters (and otherwise does nothing). Disable - it so we can handle this on our own. - """ - return "" - - -def patch_google_docstring_lookup_annotation() -> None: - """Fix issue 308: - https://github.com/tox-dev/sphinx-autodoc-typehints/issues/308 - """ - GoogleDocstring._lookup_annotation = patched_lookup_annotation # type: ignore[assignment] - - def setup(app: Sphinx) -> dict[str, bool]: app.add_config_value("always_document_param_types", False, "html") app.add_config_value("typehints_fully_qualified", False, "env") @@ -875,10 +797,7 @@ def setup(app: Sphinx) -> dict[str, bool]: app.connect("env-before-read-docs", validate_config) # config may be changed after “config-inited” event app.connect("autodoc-process-signature", process_signature) app.connect("autodoc-process-docstring", process_docstring) - fix_autodoc_typehints_for_overloaded_methods() - patch_attribute_handling(app) - patch_google_docstring_lookup_annotation() - fix_napoleon_numpy_docstring_return_type(app) + install_patches(app) return {"parallel_read_safe": True} diff --git a/src/sphinx_autodoc_typehints/patches.py b/src/sphinx_autodoc_typehints/patches.py new file mode 100644 index 00000000..ca2c9eb3 --- /dev/null +++ b/src/sphinx_autodoc_typehints/patches.py @@ -0,0 +1,96 @@ +from __future__ import annotations + +from functools import lru_cache +from typing import Any + +from sphinx.application import Sphinx +from sphinx.ext.autodoc import Options +from sphinx.ext.napoleon.docstring import GoogleDocstring + +from .attributes_patch import patch_attribute_handling + + +@lru_cache() # A cute way to make sure the function only runs once. +def fix_autodoc_typehints_for_overloaded_methods() -> None: + """ + sphinx-autodoc-typehints responds to the "autodoc-process-signature" event + to remove types from the signature line of functions. + + Normally, `FunctionDocumenter.format_signature` and + `MethodDocumenter.format_signature` call `super().format_signature` which + ends up going to `Documenter.format_signature`, and this last method emits + the `autodoc-process-signature` event. However, if there are overloads, + `FunctionDocumenter.format_signature` does something else and the event + never occurs. + + Here we remove this alternative code path by brute force. + + See https://github.com/tox-dev/sphinx-autodoc-typehints/issues/296 + """ + from sphinx.ext.autodoc import FunctionDocumenter, MethodDocumenter + + del FunctionDocumenter.format_signature + del MethodDocumenter.format_signature + + +def napoleon_numpy_docstring_return_type_processor( + app: Sphinx, what: str, name: str, obj: Any, options: Options | None, lines: list[str] # noqa: U100 +) -> None: + """Insert a : under Returns: to tell napoleon not to look for a return type.""" + if what not in ["function", "method"]: + return + if not getattr(app.config, "napoleon_numpy_docstring", False): + return + + # Search for the returns header: + # Returns: + # -------- + for idx, line in enumerate(lines[:-2]): + if line.lower().strip(":") not in ["return", "returns"]: + continue + # Underline detection. + chars = set(lines[idx + 1].strip()) + # Napoleon allows the underline to consist of a bunch of weirder things... + if len(chars) != 1 or list(chars)[0] not in "=-~_*+#": + continue + idx = idx + 2 + break + else: + return + + lines.insert(idx, ":") + + +def fix_napoleon_numpy_docstring_return_type(app: Sphinx) -> None: + """ + If no return type is explicitly provided, numpy docstrings will mess up and + use the return type text as return types. + """ + # standard priority is 500. Setting priority to 499 ensures this runs before + # napoleon's docstring processor. + app.connect("autodoc-process-docstring", napoleon_numpy_docstring_return_type_processor, priority=499) + + +def patched_lookup_annotation(*_args: Any) -> str: # noqa: U101 + """GoogleDocstring._lookup_annotation sometimes adds incorrect type + annotations to constructor parameters (and otherwise does nothing). Disable + it so we can handle this on our own. + """ + return "" + + +def patch_google_docstring_lookup_annotation() -> None: + """Fix issue 308: + https://github.com/tox-dev/sphinx-autodoc-typehints/issues/308 + """ + GoogleDocstring._lookup_annotation = patched_lookup_annotation # type: ignore[assignment] + + +def install_patches(app): + fix_autodoc_typehints_for_overloaded_methods() + patch_attribute_handling(app) + patch_google_docstring_lookup_annotation() + fix_napoleon_numpy_docstring_return_type(app) + + +___all__ = ["install_patches"]