diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9655aec3..bb7572c8 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -5,12 +5,12 @@ repos: - id: end-of-file-fixer - id: trailing-whitespace - repo: https://github.com/astral-sh/ruff-pre-commit - rev: "v0.1.14" + rev: "v0.2.0" hooks: - id: ruff args: [--fix, --exit-non-zero-on-fix] - repo: https://github.com/psf/black - rev: 23.12.1 + rev: 24.1.1 hooks: - id: black - repo: https://github.com/tox-dev/tox-ini-fmt diff --git a/src/sphinx_autodoc_typehints/__init__.py b/src/sphinx_autodoc_typehints/__init__.py index 69e1489a..792e33a4 100644 --- a/src/sphinx_autodoc_typehints/__init__.py +++ b/src/sphinx_autodoc_typehints/__init__.py @@ -1,4 +1,5 @@ """Sphinx autodoc type hints.""" + from __future__ import annotations import ast @@ -214,6 +215,13 @@ def format_annotation(annotation: Any, config: Config) -> str: # noqa: C901, PL args_format = "\\[{}]" formatted_args: str | None = "" + always_use_bars_union: bool = getattr(config, "always_use_bars_union", True) + is_bars_union = full_name == "types.UnionType" or ( + always_use_bars_union and type(annotation).__qualname__ == "_UnionGenericAlias" + ) + if is_bars_union: + full_name = "" + # Some types require special handling if full_name == "typing.NewType": args_format = f"\\(``{annotation.__name__}``, {{}})" @@ -247,7 +255,7 @@ def format_annotation(annotation: Any, config: Config) -> str: # noqa: C901, PL formatted_args = f"\\[\\[{', '.join(fmt[:-1])}], {fmt[-1]}]" elif full_name == "typing.Literal": formatted_args = f"\\[{', '.join(f'``{arg!r}``' for arg in args)}]" - elif full_name == "types.UnionType": + elif is_bars_union: return " | ".join([format_annotation(arg, config) for arg in args]) if args and not formatted_args: @@ -709,7 +717,7 @@ def _inject_signature( # noqa: C901 lines: list[str], ) -> None: for arg_name in signature.parameters: - annotation = type_hints.get(arg_name, None) + annotation = type_hints.get(arg_name) default = signature.parameters[arg_name].default @@ -928,6 +936,7 @@ def setup(app: Sphinx) -> dict[str, bool]: app.add_config_value("typehints_use_rtype", True, "env") # noqa: FBT003 app.add_config_value("typehints_defaults", None, "env") app.add_config_value("simplify_optional_unions", True, "env") # noqa: FBT003 + app.add_config_value("always_use_bars_union", False, "env") # noqa: FBT003 app.add_config_value("typehints_formatter", None, "env") app.add_config_value("typehints_use_signature", False, "env") # noqa: FBT003 app.add_config_value("typehints_use_signature_return", False, "env") # noqa: FBT003 diff --git a/src/sphinx_autodoc_typehints/attributes_patch.py b/src/sphinx_autodoc_typehints/attributes_patch.py index e0919556..439700d9 100644 --- a/src/sphinx_autodoc_typehints/attributes_patch.py +++ b/src/sphinx_autodoc_typehints/attributes_patch.py @@ -1,4 +1,5 @@ """Patch for attributes.""" + from __future__ import annotations from functools import partial diff --git a/src/sphinx_autodoc_typehints/patches.py b/src/sphinx_autodoc_typehints/patches.py index 47848f12..9fea8e2a 100644 --- a/src/sphinx_autodoc_typehints/patches.py +++ b/src/sphinx_autodoc_typehints/patches.py @@ -1,4 +1,5 @@ """Custom patches to make the world work.""" + from __future__ import annotations from functools import lru_cache diff --git a/tests/roots/test-resolve-typing-guard-tmp/demo_typing_guard.py b/tests/roots/test-resolve-typing-guard-tmp/demo_typing_guard.py index 11c28a89..aabdbc41 100644 --- a/tests/roots/test-resolve-typing-guard-tmp/demo_typing_guard.py +++ b/tests/roots/test-resolve-typing-guard-tmp/demo_typing_guard.py @@ -1,4 +1,5 @@ """Module demonstrating imports that are type guarded""" + from __future__ import annotations import datetime diff --git a/tests/roots/test-resolve-typing-guard/demo_typing_guard.py b/tests/roots/test-resolve-typing-guard/demo_typing_guard.py index 74fd474c..8b3736d5 100644 --- a/tests/roots/test-resolve-typing-guard/demo_typing_guard.py +++ b/tests/roots/test-resolve-typing-guard/demo_typing_guard.py @@ -1,4 +1,5 @@ """Module demonstrating imports that are type guarded""" + from __future__ import annotations import typing @@ -54,8 +55,7 @@ def guarded(self, item: Decimal) -> None: """ -def func(_x: Literal) -> None: - ... +def func(_x: Literal) -> None: ... __all__ = [ diff --git a/tests/test_integration.py b/tests/test_integration.py index e5085fc2..88d25b89 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -625,13 +625,11 @@ def func_with_examples() -> int: @overload -def func_with_overload(a: int, b: int) -> None: - ... +def func_with_overload(a: int, b: int) -> None: ... @overload -def func_with_overload(a: str, b: str) -> None: - ... +def func_with_overload(a: str, b: str) -> None: ... @expected( diff --git a/tests/test_sphinx_autodoc_typehints.py b/tests/test_sphinx_autodoc_typehints.py index 2c05c991..0b68ce61 100644 --- a/tests/test_sphinx_autodoc_typehints.py +++ b/tests/test_sphinx_autodoc_typehints.py @@ -80,20 +80,17 @@ class A: def get_type(self) -> type: return type(self) - class Inner: - ... + class Inner: ... class B(Generic[T]): name = "Foo" # This is set to make sure the correct class name ("B") is picked up -class C(B[str]): - ... +class C(B[str]): ... -class D(typing_extensions.Protocol): - ... +class D(typing_extensions.Protocol): ... class E(typing_extensions.Protocol[T]): # type: ignore[misc] @@ -104,8 +101,7 @@ class Slotted: __slots__ = () -class Metaclass(type): - ... +class Metaclass(type): ... class HintedMethods: @@ -369,7 +365,7 @@ def test_parse_annotation(annotation: Any, module: str, class_name: str, args: t @pytest.mark.parametrize(("annotation", "expected_result"), _CASES) def test_format_annotation(inv: Inventory, annotation: Any, expected_result: str) -> None: - conf = create_autospec(Config, _annotation_globals=globals()) + conf = create_autospec(Config, _annotation_globals=globals(), always_use_bars_union=False) result = format_annotation(annotation, conf) assert result == expected_result @@ -381,7 +377,12 @@ def test_format_annotation(inv: Inventory, annotation: Any, expected_result: str # encapsulate Union in typing.Optional expected_result_not_simplified = ":py:data:`~typing.Optional`\\ \\[" + expected_result_not_simplified expected_result_not_simplified += "]" - conf = create_autospec(Config, simplify_optional_unions=False, _annotation_globals=globals()) + conf = create_autospec( + Config, + simplify_optional_unions=False, + _annotation_globals=globals(), + always_use_bars_union=False, + ) assert format_annotation(annotation, conf) == expected_result_not_simplified # Test with the "fully_qualified" flag turned on @@ -401,7 +402,12 @@ def test_format_annotation(inv: Inventory, annotation: Any, expected_result: str expected_result = expected_result.replace("~nptyping", "nptyping") expected_result = expected_result.replace("~numpy", "numpy") expected_result = expected_result.replace("~" + __name__, __name__) - conf = create_autospec(Config, typehints_fully_qualified=True, _annotation_globals=globals()) + conf = create_autospec( + Config, + typehints_fully_qualified=True, + _annotation_globals=globals(), + always_use_bars_union=False, + ) assert format_annotation(annotation, conf) == expected_result # Test for the correct role (class vs data) using the official Sphinx inventory @@ -417,6 +423,26 @@ def test_format_annotation(inv: Inventory, annotation: Any, expected_result: str assert m.group("role") == expected_role +@pytest.mark.parametrize( + ("annotation", "expected_result"), + [ + ("int | float", ":py:class:`int` | :py:class:`float`"), + ("int | float | None", ":py:class:`int` | :py:class:`float` | :py:obj:`None`"), + ("Union[int, float]", ":py:class:`int` | :py:class:`float`"), + ("Union[int, float, None]", ":py:class:`int` | :py:class:`float` | :py:obj:`None`"), + ("Optional[int | float]", ":py:class:`int` | :py:class:`float` | :py:obj:`None`"), + ("Optional[Union[int, float]]", ":py:class:`int` | :py:class:`float` | :py:obj:`None`"), + ("Union[int | float, str]", ":py:class:`int` | :py:class:`float` | :py:class:`str`"), + ("Union[int, float] | str", ":py:class:`int` | :py:class:`float` | :py:class:`str`"), + ], +) +@pytest.mark.skipif(not PY310_PLUS, reason="| union doesn't work before py310") +def test_always_use_bars_union(annotation: str, expected_result: str) -> None: + conf = create_autospec(Config, always_use_bars_union=True) + result = format_annotation(eval(annotation), conf) # noqa: S307 + assert result == expected_result + + @pytest.mark.parametrize("library", [typing, typing_extensions], ids=["typing", "typing_extensions"]) @pytest.mark.parametrize( ("annotation", "params", "expected_result"), @@ -523,12 +549,13 @@ class dummy_module.DataClass(x) def maybe_fix_py310(expected_contents: str) -> str: + if sys.version_info >= (3, 11): + return expected_contents if not PY310_PLUS: return expected_contents.replace('"', "") for old, new in [ - ("bool | None", '"Optional"["bool"]'), - ("str | None", '"Optional"["str"]'), + ('"str" | "None"', '"Optional"["str"]'), ]: expected_contents = expected_contents.replace(old, new) return expected_contents @@ -554,15 +581,16 @@ def test_sphinx_output_future_annotations(app: SphinxTestApp, status: StringIO) Method docstring. Parameters: - * **x** (bool | None) -- foo + * **x** ("bool" | "None") -- foo * **y** ("int" | "str" | "float") -- bar - * **z** (str | None) -- baz + * **z** ("str" | "None") -- baz Return type: "str" """ + expected_contents = dedent(expected_contents) expected_contents = maybe_fix_py310(dedent(expected_contents)) assert contents == expected_contents
Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.
Alternative Proxies: