Repository: tox-dev/sphinx-autodoc-typehints
Pull request #200 (2022-01-07 04:27:27)

Fix KeyError with type hints on the first arg of bound methods
https://github.com/tox-dev/sphinx-autodoc-typehints/commit/8dc8d94912431fa79a13f39d7570637244ac1a01 introduced an issue where a bound method with a type hint on the first argument would raise a KeyError looking up the default because that argument isn't present in the signature. This PR simply ignores those parameters.


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 20c6f18..8987fa2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,6 +3,8 @@
 ## dev
 
 - Fixed `normalize_source_lines()` messing with the indentation of methods with decorators that have parameters starting with `def`.
+- Handle `ValueError` or `TypeError` being raised when signature of an object cannot be determined
+- Fix `KeyError` being thrown when argument is not documented (e.g. `cls` argument for class methods, and `self` for methods)
 
 ## 1.14.0
 
diff --git a/src/sphinx_autodoc_typehints/__init__.py b/src/sphinx_autodoc_typehints/__init__.py
index 39f70c7..33679ca 100644
--- a/src/sphinx_autodoc_typehints/__init__.py
+++ b/src/sphinx_autodoc_typehints/__init__.py
@@ -454,7 +454,10 @@ def process_docstring(
     for arg_name, annotation in type_hints.items():
         if arg_name == "return":
             continue  # this is handled separately later
-        default = inspect.Parameter.empty if signature is None else signature.parameters[arg_name].default
+        if signature is None or arg_name not in signature.parameters:
+            default = inspect.Parameter.empty
+        else:
+            default = signature.parameters[arg_name].default
         if arg_name.endswith("_"):
             arg_name = f"{arg_name[:-1]}\\_"
 


Test patch:
diff --git a/tests/test_sphinx_autodoc_typehints.py b/tests/test_sphinx_autodoc_typehints.py
index 22ee588..a7a9d43 100644
--- a/tests/test_sphinx_autodoc_typehints.py
+++ b/tests/test_sphinx_autodoc_typehints.py
@@ -7,7 +7,7 @@ import typing
 from functools import cmp_to_key
 from io import StringIO
 from textwrap import dedent, indent
-from types import ModuleType
+from types import FunctionType, ModuleType
 from typing import (
     IO,
     Any,
@@ -54,7 +54,7 @@ class A:
         return type(self)
 
     class Inner:
-        pass
+        ...
 
 
 class B(Generic[T]):
@@ -63,15 +63,15 @@ class B(Generic[T]):
 
 
 class C(B[str]):
-    pass
+    ...
 
 
 class D(typing_extensions.Protocol):
-    pass
+    ...
 
 
 class E(typing_extensions.Protocol[T]):  # type: ignore #  Invariant type variable "T" used in protocol where covariant
-    pass
+    ...
 
 
 class Slotted:
@@ -79,7 +79,16 @@ class Slotted:
 
 
 class Metaclass(type):
-    pass
+    ...
+
+
+class HintedMethods:
+    @classmethod
+    def from_magic(cls: type[T]) -> T:
+        ...
+
+    def method(self: T) -> T:
+        ...
 
 
 PY310_PLUS = sys.version_info >= (3, 10)
@@ -673,13 +682,13 @@ def test_normalize_source_lines_async_def() -> None:
     source = """
     async def async_function():
         class InnerClass:
-            def __init__(self): pass
+            def __init__(self): ...
     """
 
     expected = """
     async def async_function():
         class InnerClass:
-            def __init__(self): pass
+            def __init__(self): ...
     """
 
     assert normalize_source_lines(dedent(source)) == dedent(expected)
@@ -699,7 +708,7 @@ def test_normalize_source_lines_def_starting_decorator_parameter() -> None:
         ),
     )
     def __init__(bound_args):  # noqa: N805
-        pass
+        ...
     """
 
     expected = """
@@ -715,7 +724,7 @@ def test_normalize_source_lines_def_starting_decorator_parameter() -> None:
         ),
     )
     def __init__(bound_args):  # noqa: N805
-        pass
+        ...
     """
 
     assert normalize_source_lines(dedent(source)) == dedent(expected)
@@ -728,3 +737,17 @@ def test_default_no_signature(obj: Any) -> None:
     lines: list[str] = []
     process_docstring(app, "what", "name", obj, None, lines)
     assert lines == []
+
+
+@pytest.mark.parametrize("method", [HintedMethods.from_magic, HintedMethods().method])
+def test_bound_class_method(method: FunctionType) -> None:
+    config = create_autospec(
+        Config,
+        typehints_fully_qualified=False,
+        simplify_optional_unions=False,
+        typehints_document_rtype=False,
+        always_document_param_types=True,
+        typehints_defaults=True,
+    )
+    app: Sphinx = create_autospec(Sphinx, config=config)
+    process_docstring(app, "class", method.__qualname__, method, None, [])