Repository: tox-dev/sphinx-autodoc-typehints
Pull request #247 (2022-07-27 01:00:35)

Check 3.11 support
https://github.com/python/typing/issues/275


Patch:
diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml
index eae896e..d42b632 100644
--- a/.github/workflows/check.yml
+++ b/.github/workflows/check.yml
@@ -12,11 +12,12 @@ concurrency:
 jobs:
   test:
     name: test with CPython ${{ matrix.py }}
-    runs-on: ubuntu-20.04
+    runs-on: ubuntu-22.04
     strategy:
       fail-fast: false
       matrix:
         py:
+          - "3.11.0-beta.4"
           - "3.10"
           - "3.9"
           - "3.8"
@@ -66,7 +67,7 @@ jobs:
 
   coverage:
     name: Combine coverage
-    runs-on: ubuntu-20.04
+    runs-on: ubuntu-22.04
     needs: test
     steps:
       - uses: actions/checkout@v3
@@ -98,7 +99,7 @@ jobs:
 
   check:
     name: tox env ${{ matrix.tox_env }}
-    runs-on: ubuntu-20.04
+    runs-on: ubuntu-22.04
     strategy:
       fail-fast: false
       matrix:
@@ -124,7 +125,7 @@ jobs:
   publish:
     if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags')
     needs: [check, coverage]
-    runs-on: ubuntu-20.04
+    runs-on: ubuntu-22.04
     steps:
       - name: Setup python to build package
         uses: actions/setup-python@v4
@@ -138,7 +139,7 @@ jobs:
       - name: Build sdist and wheel
         run: python -m build -s -w . -o dist
       - name: Publish to PyPi
-        uses: pypa/gh-action-pypi-publish@master
+        uses: pypa/gh-action-pypi-publish@v1.5.1
         with:
           skip_existing: true
           user: __token__
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e95a493..37cdc1f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,10 @@
 # Changelog
 
+## 1.19.0
+
+- Support for CPython 3.11, no longer adds `Optional` when the argument is default per
+  [recommendation from PEP-484](https://github.com/tox-dev/sphinx-autodoc-typehints/pull/247).
+
 ## 1.18.3
 
 - Support and require `nptyping>=2.1.2`
diff --git a/README.md b/README.md
index 4c38543..2ec4c83 100644
--- a/README.md
+++ b/README.md
@@ -64,7 +64,7 @@ The following configuration options are accepted:
   directive, if present, otherwise fall back to using `:rtype:`. Use in conjunction with
   [napoleon_use_rtype](https://www.sphinx-doc.org/en/master/usage/extensions/napoleon.html#confval-napoleon_use_rtype)
   to avoid generation of duplicate or redundant return type information.
-- `typehints_defaults` (default: `None`): If `None`, defaults are not added. Otherwise adds a default annotation:
+- `typehints_defaults` (default: `None`): If `None`, defaults are not added. Otherwise, adds a default annotation:
 
   - `'comma'` adds it after the type, changing Sphinx’ default look to “**param** (_int_, default: `1`) -- text”.
   - `'braces'` adds `(default: ...)` after the type (useful for numpydoc like styles).
diff --git a/setup.cfg b/setup.cfg
index 56f3218..009b23d 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -72,7 +72,7 @@ other =
     *\sphinx-autodoc-typehints
 
 [coverage:report]
-fail_under = 82
+fail_under = 81
 
 [coverage:html]
 show_contexts = true
diff --git a/src/sphinx_autodoc_typehints/__init__.py b/src/sphinx_autodoc_typehints/__init__.py
index 1a9952f..4cddf21 100644
--- a/src/sphinx_autodoc_typehints/__init__.py
+++ b/src/sphinx_autodoc_typehints/__init__.py
@@ -90,7 +90,10 @@ def get_annotation_args(annotation: Any, module: str, class_name: str) -> tuple[
         return annotation.__values__  # type: ignore # deduced Any
     elif class_name == "Generic":
         return annotation.__parameters__  # type: ignore # deduced Any
-    return getattr(annotation, "__args__", ())
+    result = getattr(annotation, "__args__", ())
+    # 3.10 and earlier Tuple[()] returns ((), ) instead of () the tuple does
+    result = () if len(result) == 1 and result[0] == () else result  # type: ignore
+    return result
 
 
 def format_internal_tuple(t: tuple[Any, ...], config: Config) -> str:
@@ -161,7 +164,7 @@ def format_annotation(annotation: Any, config: Config) -> str:  # noqa: C901 # t
         formatted_args = None if args else args_format
     elif full_name == "typing.Optional":
         args = tuple(x for x in args if x is not type(None))  # noqa: E721
-    elif full_name == "typing.Union" and type(None) in args:
+    elif full_name in ("typing.Union", "types.UnionType") and type(None) in args:
         if len(args) == 2:
             full_name = "typing.Optional"
             args = tuple(x for x in args if x is not type(None))  # noqa: E721
diff --git a/tox.ini b/tox.ini
index 828be59..8ef4fc9 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,6 +1,7 @@
 [tox]
 envlist =
     fix
+    py311
     py310
     py39
     py38
@@ -45,7 +46,7 @@ description = run type check on code base
 setenv =
     {tty:MYPY_FORCE_COLOR = 1}
 deps =
-    mypy==0.961
+    mypy==0.971
     types-docutils
 commands =
     mypy --python-version 3.10 src
@@ -71,6 +72,7 @@ commands =
     coverage html -d {toxworkdir}/htmlcov
     diff-cover --compare-branch {env:DIFF_AGAINST:origin/main} {toxworkdir}/coverage.xml
 depends =
+    py311
     py310
     py39
     py38


Test patch:
diff --git a/tests/roots/test-dummy/dummy_module.py b/tests/roots/test-dummy/dummy_module.py
index c78e94e..0f6dc3c 100644
--- a/tests/roots/test-dummy/dummy_module.py
+++ b/tests/roots/test-dummy/dummy_module.py
@@ -1,7 +1,7 @@
 import typing
 from dataclasses import dataclass
 from mailbox import Mailbox
-from typing import Callable, Union
+from typing import Callable, Optional, Union
 
 
 def get_local_function():
@@ -191,7 +191,7 @@ test
 
 
 def function_with_typehint_comment_not_inline(x=None, *y, z, **kwargs):  # noqa: U100
-    # type: (Union[str, bytes], *str, bytes, **int) -> None
+    # type: (Union[str, bytes, None], *str, bytes, **int) -> None
     """
     Function docstring.
 
@@ -210,7 +210,7 @@ class ClassWithTypehintsNotInline:
     """
 
     def __init__(self, x=None):  # noqa: U100
-        # type: (Callable[[int, bytes], int]) -> None
+        # type: (Optional[Callable[[int, bytes], int]]) -> None
         pass
 
     def foo(self, x=1):
@@ -224,7 +224,7 @@ class ClassWithTypehintsNotInline:
 
     @classmethod
     def mk(cls, x=None):
-        # type: (Callable[[int, bytes], int]) -> ClassWithTypehintsNotInline
+        # type: (Optional[Callable[[int, bytes], int]]) -> ClassWithTypehintsNotInline
         """
         Method docstring.
 
diff --git a/tests/test_sphinx_autodoc_typehints.py b/tests/test_sphinx_autodoc_typehints.py
index e0376f9..b03ae2c 100644
--- a/tests/test_sphinx_autodoc_typehints.py
+++ b/tests/test_sphinx_autodoc_typehints.py
@@ -232,7 +232,7 @@ def test_parse_annotation(annotation: Any, module: str, class_name: str, args: t
         (S, ":py:class:`~typing.TypeVar`\\(``S``, bound= miss)"),
         # ## These test for correct internal tuple rendering, even if not all are valid Tuple types
         # Zero-length tuple remains
-        (Tuple[()], ":py:data:`~typing.Tuple`\\[()]"),
+        (Tuple[()], ":py:data:`~typing.Tuple`"),
         # Internal single tuple with simple types is flattened in the output
         (Tuple[(int,)], ":py:data:`~typing.Tuple`\\[:py:class:`int`]"),
         (Tuple[(int, int)], ":py:data:`~typing.Tuple`\\[:py:class:`int`, :py:class:`int`]"),
@@ -374,7 +374,7 @@ def set_python_path() -> None:
 def maybe_fix_py310(expected_contents: str) -> str:
     if PY310_PLUS:
         for old, new in [
-            ("*bool** | **None*", '"bool" | "None"'),
+            ("*bool** | **None*", '"Optional"["bool"]'),
             ("*int** | **str** | **float*", '"int" | "str" | "float"'),
             ("*str** | **None*", '"Optional"["str"]'),
             ("(*bool*)", '("bool")'),
@@ -719,7 +719,8 @@ def test_sphinx_output_future_annotations(app: SphinxTestApp, status: StringIO)
            Return type:
               str
         """
-        assert text_contents == maybe_fix_py310(dedent(expected_contents))
+        expected_contents = maybe_fix_py310(dedent(expected_contents))
+        assert text_contents == expected_contents
 
 
 @pytest.mark.parametrize(