Skip to content

Commit abc497f

Browse files
Handle collections.abc.Callable as well as typing.Callable (#289)
* Handle collections.abc.Callable as well as typing.Callable typing.Callable is deprecated in favor of collections.abc.Callable since Python 3.9: https://docs.python.org/3/library/typing.html?highlight=typing%20callable#typing.Callable * Add tests * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Fix tests in py38 and py37 * Fix lints * Suppress mypy lints Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent e8cdce1 commit abc497f

File tree

2 files changed

+29
-1
lines changed

2 files changed

+29
-1
lines changed

src/sphinx_autodoc_typehints/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ def format_annotation(annotation: Any, config: Config) -> str: # noqa: C901 # t
177177
role = "data"
178178
args_format = f"\\[:py:data:`{prefix}typing.Union`\\[{{}}]]"
179179
args = tuple(x for x in args if x is not type(None)) # noqa: E721
180-
elif full_name == "typing.Callable" and args and args[0] is not ...:
180+
elif full_name in ("typing.Callable", "collections.abc.Callable") and args and args[0] is not ...:
181181
fmt = [format_annotation(arg, config) for arg in args]
182182
formatted_args = f"\\[\\[{', '.join(fmt[:-1])}], {fmt[-1]}]"
183183
elif full_name == "typing.Literal":

tests/test_sphinx_autodoc_typehints.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,23 @@ def method(self: T) -> T:
111111

112112
PY310_PLUS = sys.version_info >= (3, 10)
113113

114+
if sys.version_info >= (3, 9):
115+
AbcCallable = collections.abc.Callable # type: ignore[type-arg]
116+
else:
117+
# Hacks to make it work the same in old versions.
118+
# We could also set AbcCallable = typing.Callable and x fail the tests that
119+
# use AbcCallable when in versions less than 3.9.
120+
class MyGenericAlias(typing._VariadicGenericAlias, _root=True): # noqa: SC200
121+
def __getitem__(self, params): # noqa: SC200
122+
result = super().__getitem__(params) # noqa: SC200
123+
# Make a copy so we don't change the name of a cached annotation
124+
result = result.copy_with(result.__args__)
125+
result.__module__ = "collections.abc"
126+
return result
127+
128+
AbcCallable = MyGenericAlias(collections.abc.Callable, (), special=True)
129+
AbcCallable.__module__ = "collections.abc"
130+
114131

115132
@pytest.mark.parametrize(
116133
("annotation", "module", "class_name", "args"),
@@ -128,6 +145,13 @@ def method(self: T) -> T:
128145
pytest.param(Callable, "typing", "Callable", (), id="Callable"),
129146
pytest.param(Callable[..., str], "typing", "Callable", (..., str), id="Callable_returntype"),
130147
pytest.param(Callable[[int, str], str], "typing", "Callable", (int, str, str), id="Callable_all_types"),
148+
pytest.param(
149+
AbcCallable[[int, str], str], # type: ignore[misc]
150+
"collections.abc",
151+
"Callable",
152+
(int, str, str),
153+
id="collections.abc.Callable_all_types",
154+
),
131155
pytest.param(Pattern, "typing", "Pattern", (), id="Pattern"),
132156
pytest.param(Pattern[str], "typing", "Pattern", (str,), id="Pattern_parametrized"),
133157
pytest.param(Match, "typing", "Match", (), id="Match"),
@@ -224,6 +248,10 @@ def test_parse_annotation(annotation: Any, module: str, class_name: str, args: t
224248
":py:data:`~typing.Callable`\\[\\[:py:class:`~typing.TypeVar`\\(``T``)],"
225249
" :py:class:`~typing.TypeVar`\\(``T``)]",
226250
),
251+
(
252+
AbcCallable[[int, str], bool], # type: ignore[misc]
253+
":py:class:`~collections.abc.Callable`\\[\\[:py:class:`int`, " ":py:class:`str`], :py:class:`bool`]",
254+
),
227255
(Pattern, ":py:class:`~typing.Pattern`"),
228256
(Pattern[str], ":py:class:`~typing.Pattern`\\[:py:class:`str`]"),
229257
(IO, ":py:class:`~typing.IO`"),

0 commit comments

Comments
 (0)