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

NFC: Move patches to a separate file #314

Merged
merged 2 commits into from Jan 23, 2023
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
85 changes: 2 additions & 83 deletions src/sphinx_autodoc_typehints/__init__.py
Expand Up @@ -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
Expand All @@ -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__)
Expand Down Expand Up @@ -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")
Expand All @@ -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}


Expand Down
96 changes: 96 additions & 0 deletions 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"]