diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index eae896eb..d42b632c 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 e95a493d..37cdc1fc 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 4c38543a..2ec4c832 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 56f32189..009b23d8 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 1a9952f5..4cddf21f 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/tests/roots/test-dummy/dummy_module.py b/tests/roots/test-dummy/dummy_module.py index c78e94ee..0f6dc3c4 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 @@ def method_without_typehint(self, x): # noqa: U100 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 @@ def foo(self, x=1): @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 e0376f92..b03ae2c9 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( diff --git a/tox.ini b/tox.ini index 828be598..8ef4fc92 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