Skip to content

Commit

Permalink
NFC: Move patches to a separate file (#314)
Browse files Browse the repository at this point in the history
* NFC: Move patches to a separate file

* Import __anotations__
  • Loading branch information
hoodmane committed Jan 23, 2023
1 parent 756049d commit 2653e7d
Show file tree
Hide file tree
Showing 2 changed files with 98 additions and 83 deletions.
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"]

0 comments on commit 2653e7d

Please sign in to comment.