Skip to content

Commit 5a55856

Browse files
authored
Properly format collections.abc.Callable (#12314)
1 parent 1ff9adf commit 5a55856

File tree

2 files changed

+65
-34
lines changed

2 files changed

+65
-34
lines changed

sphinx/util/typing.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@ def _is_annotated_form(obj: Any) -> TypeGuard[Annotated[Any, ...]]:
178178
return typing.get_origin(obj) is Annotated or str(obj).startswith('typing.Annotated')
179179

180180

181-
def _get_typing_internal_name(obj: Any) -> str | None:
181+
def _typing_internal_name(obj: Any) -> str | None:
182182
if sys.version_info[:2] >= (3, 10):
183183
return obj.__name__
184184
return getattr(obj, '_name', None)
@@ -252,7 +252,7 @@ def restify(cls: Any, mode: _RestifyMode = 'fully-qualified-except-typing') -> s
252252
# *cls* is defined in ``typing``, and thus ``__args__`` must exist
253253
return ' | '.join(restify(a, mode) for a in cls.__args__)
254254
elif inspect.isgenericalias(cls):
255-
cls_name = _get_typing_internal_name(cls)
255+
cls_name = _typing_internal_name(cls)
256256

257257
if isinstance(cls.__origin__, typing._SpecialForm):
258258
# ClassVar; Concatenate; Final; Literal; Unpack; TypeGuard
@@ -271,12 +271,15 @@ def restify(cls: Any, mode: _RestifyMode = 'fully-qualified-except-typing') -> s
271271
return text
272272

273273
# Callable has special formatting
274-
if cls_module_is_typing and _get_typing_internal_name(cls) == 'Callable':
274+
if (
275+
(cls_module_is_typing and _typing_internal_name(cls) == 'Callable')
276+
or (cls.__module__ == 'collections.abc' and cls.__name__ == 'Callable')
277+
):
275278
args = ', '.join(restify(a, mode) for a in __args__[:-1])
276279
returns = restify(__args__[-1], mode)
277280
return fr'{text}\ [[{args}], {returns}]'
278281

279-
if cls_module_is_typing and _get_typing_internal_name(cls.__origin__) == 'Literal':
282+
if cls_module_is_typing and _typing_internal_name(cls.__origin__) == 'Literal':
280283
args = ', '.join(_format_literal_arg_restify(a, mode=mode)
281284
for a in cls.__args__)
282285
return fr'{text}\ [{args}]'
@@ -285,7 +288,7 @@ def restify(cls: Any, mode: _RestifyMode = 'fully-qualified-except-typing') -> s
285288
args = ', '.join(restify(a, mode) for a in __args__)
286289
return fr'{text}\ [{args}]'
287290
elif isinstance(cls, typing._SpecialForm):
288-
cls_name = _get_typing_internal_name(cls)
291+
cls_name = _typing_internal_name(cls)
289292
return f':py:obj:`~{cls.__module__}.{cls_name}`'
290293
elif sys.version_info[:2] >= (3, 11) and cls is typing.Any:
291294
# handle bpo-46998

tests/test_util/test_util_typing.py

Lines changed: 57 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
"""Tests util.typing functions."""
22

33
import sys
4+
import typing as t
5+
from collections import abc
46
from contextvars import Context, ContextVar, Token
57
from enum import Enum
68
from numbers import Integral
@@ -29,10 +31,7 @@
2931
)
3032
from typing import (
3133
Any,
32-
Callable,
3334
Dict,
34-
Generator,
35-
Iterator,
3635
List,
3736
NewType,
3837
Optional,
@@ -173,20 +172,29 @@ def test_restify_type_hints_containers():
173172
assert restify(MyList[Tuple[int, int]]) == (":py:class:`tests.test_util.test_util_typing.MyList`\\ "
174173
"[:py:class:`~typing.Tuple`\\ "
175174
"[:py:class:`int`, :py:class:`int`]]")
176-
assert restify(Generator[None, None, None]) == (":py:class:`~typing.Generator`\\ "
177-
"[:py:obj:`None`, :py:obj:`None`, "
178-
":py:obj:`None`]")
179-
assert restify(Iterator[None]) == (":py:class:`~typing.Iterator`\\ "
180-
"[:py:obj:`None`]")
175+
assert restify(t.Generator[None, None, None]) == (":py:class:`~typing.Generator`\\ "
176+
"[:py:obj:`None`, :py:obj:`None`, "
177+
":py:obj:`None`]")
178+
assert restify(abc.Generator[None, None, None]) == (":py:class:`collections.abc.Generator`\\ "
179+
"[:py:obj:`None`, :py:obj:`None`, "
180+
":py:obj:`None`]")
181+
assert restify(t.Iterator[None]) == (":py:class:`~typing.Iterator`\\ "
182+
"[:py:obj:`None`]")
183+
assert restify(abc.Iterator[None]) == (":py:class:`collections.abc.Iterator`\\ "
184+
"[:py:obj:`None`]")
181185

182186

183187
def test_restify_type_hints_Callable():
184-
assert restify(Callable) == ":py:class:`~typing.Callable`"
185-
186-
assert restify(Callable[[str], int]) == (":py:class:`~typing.Callable`\\ "
187-
"[[:py:class:`str`], :py:class:`int`]")
188-
assert restify(Callable[..., int]) == (":py:class:`~typing.Callable`\\ "
189-
"[[...], :py:class:`int`]")
188+
assert restify(t.Callable) == ":py:class:`~typing.Callable`"
189+
assert restify(t.Callable[[str], int]) == (":py:class:`~typing.Callable`\\ "
190+
"[[:py:class:`str`], :py:class:`int`]")
191+
assert restify(t.Callable[..., int]) == (":py:class:`~typing.Callable`\\ "
192+
"[[...], :py:class:`int`]")
193+
assert restify(abc.Callable) == ":py:class:`collections.abc.Callable`"
194+
assert restify(abc.Callable[[str], int]) == (":py:class:`collections.abc.Callable`\\ "
195+
"[[:py:class:`str`], :py:class:`int`]")
196+
assert restify(abc.Callable[..., int]) == (":py:class:`collections.abc.Callable`\\ "
197+
"[[...], :py:class:`int`]")
190198

191199

192200
def test_restify_type_hints_Union():
@@ -409,13 +417,21 @@ def test_stringify_type_hints_containers():
409417
assert stringify_annotation(MyList[Tuple[int, int]], "fully-qualified") == "tests.test_util.test_util_typing.MyList[typing.Tuple[int, int]]"
410418
assert stringify_annotation(MyList[Tuple[int, int]], "smart") == "~tests.test_util.test_util_typing.MyList[~typing.Tuple[int, int]]"
411419

412-
assert stringify_annotation(Generator[None, None, None], 'fully-qualified-except-typing') == "Generator[None, None, None]"
413-
assert stringify_annotation(Generator[None, None, None], "fully-qualified") == "typing.Generator[None, None, None]"
414-
assert stringify_annotation(Generator[None, None, None], "smart") == "~typing.Generator[None, None, None]"
420+
assert stringify_annotation(t.Generator[None, None, None], 'fully-qualified-except-typing') == "Generator[None, None, None]"
421+
assert stringify_annotation(t.Generator[None, None, None], "fully-qualified") == "typing.Generator[None, None, None]"
422+
assert stringify_annotation(t.Generator[None, None, None], "smart") == "~typing.Generator[None, None, None]"
423+
424+
assert stringify_annotation(abc.Generator[None, None, None], 'fully-qualified-except-typing') == "collections.abc.Generator[None, None, None]"
425+
assert stringify_annotation(abc.Generator[None, None, None], "fully-qualified") == "collections.abc.Generator[None, None, None]"
426+
assert stringify_annotation(abc.Generator[None, None, None], "smart") == "~collections.abc.Generator[None, None, None]"
427+
428+
assert stringify_annotation(t.Iterator[None], 'fully-qualified-except-typing') == "Iterator[None]"
429+
assert stringify_annotation(t.Iterator[None], "fully-qualified") == "typing.Iterator[None]"
430+
assert stringify_annotation(t.Iterator[None], "smart") == "~typing.Iterator[None]"
415431

416-
assert stringify_annotation(Iterator[None], 'fully-qualified-except-typing') == "Iterator[None]"
417-
assert stringify_annotation(Iterator[None], "fully-qualified") == "typing.Iterator[None]"
418-
assert stringify_annotation(Iterator[None], "smart") == "~typing.Iterator[None]"
432+
assert stringify_annotation(abc.Iterator[None], 'fully-qualified-except-typing') == "collections.abc.Iterator[None]"
433+
assert stringify_annotation(abc.Iterator[None], "fully-qualified") == "collections.abc.Iterator[None]"
434+
assert stringify_annotation(abc.Iterator[None], "smart") == "~collections.abc.Iterator[None]"
419435

420436

421437
def test_stringify_type_hints_pep_585():
@@ -489,17 +505,29 @@ def test_stringify_type_hints_string():
489505

490506

491507
def test_stringify_type_hints_Callable():
492-
assert stringify_annotation(Callable, 'fully-qualified-except-typing') == "Callable"
493-
assert stringify_annotation(Callable, "fully-qualified") == "typing.Callable"
494-
assert stringify_annotation(Callable, "smart") == "~typing.Callable"
508+
assert stringify_annotation(t.Callable, 'fully-qualified-except-typing') == "Callable"
509+
assert stringify_annotation(t.Callable, "fully-qualified") == "typing.Callable"
510+
assert stringify_annotation(t.Callable, "smart") == "~typing.Callable"
511+
512+
assert stringify_annotation(t.Callable[[str], int], 'fully-qualified-except-typing') == "Callable[[str], int]"
513+
assert stringify_annotation(t.Callable[[str], int], "fully-qualified") == "typing.Callable[[str], int]"
514+
assert stringify_annotation(t.Callable[[str], int], "smart") == "~typing.Callable[[str], int]"
515+
516+
assert stringify_annotation(t.Callable[..., int], 'fully-qualified-except-typing') == "Callable[[...], int]"
517+
assert stringify_annotation(t.Callable[..., int], "fully-qualified") == "typing.Callable[[...], int]"
518+
assert stringify_annotation(t.Callable[..., int], "smart") == "~typing.Callable[[...], int]"
519+
520+
assert stringify_annotation(abc.Callable, 'fully-qualified-except-typing') == "collections.abc.Callable"
521+
assert stringify_annotation(abc.Callable, "fully-qualified") == "collections.abc.Callable"
522+
assert stringify_annotation(abc.Callable, "smart") == "~collections.abc.Callable"
495523

496-
assert stringify_annotation(Callable[[str], int], 'fully-qualified-except-typing') == "Callable[[str], int]"
497-
assert stringify_annotation(Callable[[str], int], "fully-qualified") == "typing.Callable[[str], int]"
498-
assert stringify_annotation(Callable[[str], int], "smart") == "~typing.Callable[[str], int]"
524+
assert stringify_annotation(abc.Callable[[str], int], 'fully-qualified-except-typing') == "collections.abc.Callable[[str], int]"
525+
assert stringify_annotation(abc.Callable[[str], int], "fully-qualified") == "collections.abc.Callable[[str], int]"
526+
assert stringify_annotation(abc.Callable[[str], int], "smart") == "~collections.abc.Callable[[str], int]"
499527

500-
assert stringify_annotation(Callable[..., int], 'fully-qualified-except-typing') == "Callable[[...], int]"
501-
assert stringify_annotation(Callable[..., int], "fully-qualified") == "typing.Callable[[...], int]"
502-
assert stringify_annotation(Callable[..., int], "smart") == "~typing.Callable[[...], int]"
528+
assert stringify_annotation(abc.Callable[..., int], 'fully-qualified-except-typing') == "collections.abc.Callable[[...], int]"
529+
assert stringify_annotation(abc.Callable[..., int], "fully-qualified") == "collections.abc.Callable[[...], int]"
530+
assert stringify_annotation(abc.Callable[..., int], "smart") == "~collections.abc.Callable[[...], int]"
503531

504532

505533
def test_stringify_type_hints_Union():

0 commit comments

Comments
 (0)