Skip to content

Commit 601583f

Browse files
Add option to force unions (and options) to be rendered with bars (#418)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent 2156242 commit 601583f

File tree

2 files changed

+48
-8
lines changed

2 files changed

+48
-8
lines changed

src/sphinx_autodoc_typehints/__init__.py

+9-1
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,13 @@ def format_annotation(annotation: Any, config: Config) -> str: # noqa: C901, PL
215215
args_format = "\\[{}]"
216216
formatted_args: str | None = ""
217217

218+
always_use_bars_union: bool = getattr(config, "always_use_bars_union", True)
219+
is_bars_union = full_name == "types.UnionType" or (
220+
always_use_bars_union and type(annotation).__qualname__ == "_UnionGenericAlias"
221+
)
222+
if is_bars_union:
223+
full_name = ""
224+
218225
# Some types require special handling
219226
if full_name == "typing.NewType":
220227
args_format = f"\\(``{annotation.__name__}``, {{}})"
@@ -248,7 +255,7 @@ def format_annotation(annotation: Any, config: Config) -> str: # noqa: C901, PL
248255
formatted_args = f"\\[\\[{', '.join(fmt[:-1])}], {fmt[-1]}]"
249256
elif full_name == "typing.Literal":
250257
formatted_args = f"\\[{', '.join(f'``{arg!r}``' for arg in args)}]"
251-
elif full_name == "types.UnionType":
258+
elif is_bars_union:
252259
return " | ".join([format_annotation(arg, config) for arg in args])
253260

254261
if args and not formatted_args:
@@ -929,6 +936,7 @@ def setup(app: Sphinx) -> dict[str, bool]:
929936
app.add_config_value("typehints_use_rtype", True, "env") # noqa: FBT003
930937
app.add_config_value("typehints_defaults", None, "env")
931938
app.add_config_value("simplify_optional_unions", True, "env") # noqa: FBT003
939+
app.add_config_value("always_use_bars_union", False, "env") # noqa: FBT003
932940
app.add_config_value("typehints_formatter", None, "env")
933941
app.add_config_value("typehints_use_signature", False, "env") # noqa: FBT003
934942
app.add_config_value("typehints_use_signature_return", False, "env") # noqa: FBT003

tests/test_sphinx_autodoc_typehints.py

+39-7
Original file line numberDiff line numberDiff line change
@@ -365,7 +365,7 @@ def test_parse_annotation(annotation: Any, module: str, class_name: str, args: t
365365

366366
@pytest.mark.parametrize(("annotation", "expected_result"), _CASES)
367367
def test_format_annotation(inv: Inventory, annotation: Any, expected_result: str) -> None:
368-
conf = create_autospec(Config, _annotation_globals=globals())
368+
conf = create_autospec(Config, _annotation_globals=globals(), always_use_bars_union=False)
369369
result = format_annotation(annotation, conf)
370370
assert result == expected_result
371371

@@ -377,7 +377,12 @@ def test_format_annotation(inv: Inventory, annotation: Any, expected_result: str
377377
# encapsulate Union in typing.Optional
378378
expected_result_not_simplified = ":py:data:`~typing.Optional`\\ \\[" + expected_result_not_simplified
379379
expected_result_not_simplified += "]"
380-
conf = create_autospec(Config, simplify_optional_unions=False, _annotation_globals=globals())
380+
conf = create_autospec(
381+
Config,
382+
simplify_optional_unions=False,
383+
_annotation_globals=globals(),
384+
always_use_bars_union=False,
385+
)
381386
assert format_annotation(annotation, conf) == expected_result_not_simplified
382387

383388
# Test with the "fully_qualified" flag turned on
@@ -397,7 +402,12 @@ def test_format_annotation(inv: Inventory, annotation: Any, expected_result: str
397402
expected_result = expected_result.replace("~nptyping", "nptyping")
398403
expected_result = expected_result.replace("~numpy", "numpy")
399404
expected_result = expected_result.replace("~" + __name__, __name__)
400-
conf = create_autospec(Config, typehints_fully_qualified=True, _annotation_globals=globals())
405+
conf = create_autospec(
406+
Config,
407+
typehints_fully_qualified=True,
408+
_annotation_globals=globals(),
409+
always_use_bars_union=False,
410+
)
401411
assert format_annotation(annotation, conf) == expected_result
402412

403413
# Test for the correct role (class vs data) using the official Sphinx inventory
@@ -413,6 +423,26 @@ def test_format_annotation(inv: Inventory, annotation: Any, expected_result: str
413423
assert m.group("role") == expected_role
414424

415425

426+
@pytest.mark.parametrize(
427+
("annotation", "expected_result"),
428+
[
429+
("int | float", ":py:class:`int` | :py:class:`float`"),
430+
("int | float | None", ":py:class:`int` | :py:class:`float` | :py:obj:`None`"),
431+
("Union[int, float]", ":py:class:`int` | :py:class:`float`"),
432+
("Union[int, float, None]", ":py:class:`int` | :py:class:`float` | :py:obj:`None`"),
433+
("Optional[int | float]", ":py:class:`int` | :py:class:`float` | :py:obj:`None`"),
434+
("Optional[Union[int, float]]", ":py:class:`int` | :py:class:`float` | :py:obj:`None`"),
435+
("Union[int | float, str]", ":py:class:`int` | :py:class:`float` | :py:class:`str`"),
436+
("Union[int, float] | str", ":py:class:`int` | :py:class:`float` | :py:class:`str`"),
437+
],
438+
)
439+
@pytest.mark.skipif(not PY310_PLUS, reason="| union doesn't work before py310")
440+
def test_always_use_bars_union(annotation: str, expected_result: str) -> None:
441+
conf = create_autospec(Config, always_use_bars_union=True)
442+
result = format_annotation(eval(annotation), conf) # noqa: S307
443+
assert result == expected_result
444+
445+
416446
@pytest.mark.parametrize("library", [typing, typing_extensions], ids=["typing", "typing_extensions"])
417447
@pytest.mark.parametrize(
418448
("annotation", "params", "expected_result"),
@@ -519,12 +549,13 @@ class dummy_module.DataClass(x)
519549

520550

521551
def maybe_fix_py310(expected_contents: str) -> str:
552+
if sys.version_info >= (3, 11):
553+
return expected_contents
522554
if not PY310_PLUS:
523555
return expected_contents.replace('"', "")
524556

525557
for old, new in [
526-
("bool | None", '"Optional"["bool"]'),
527-
("str | None", '"Optional"["str"]'),
558+
('"str" | "None"', '"Optional"["str"]'),
528559
]:
529560
expected_contents = expected_contents.replace(old, new)
530561
return expected_contents
@@ -550,15 +581,16 @@ def test_sphinx_output_future_annotations(app: SphinxTestApp, status: StringIO)
550581
Method docstring.
551582
552583
Parameters:
553-
* **x** (bool | None) -- foo
584+
* **x** ("bool" | "None") -- foo
554585
555586
* **y** ("int" | "str" | "float") -- bar
556587
557-
* **z** (str | None) -- baz
588+
* **z** ("str" | "None") -- baz
558589
559590
Return type:
560591
"str"
561592
"""
593+
expected_contents = dedent(expected_contents)
562594
expected_contents = maybe_fix_py310(dedent(expected_contents))
563595
assert contents == expected_contents
564596

0 commit comments

Comments
 (0)