Skip to content

Commit

Permalink
Handle collections.abc.Callable as well as typing.Callable (#289)
Browse files Browse the repository at this point in the history
* 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>
  • Loading branch information
hoodmane and pre-commit-ci[bot] committed Jan 14, 2023
1 parent e8cdce1 commit abc497f
Show file tree
Hide file tree
Showing 2 changed files with 29 additions and 1 deletion.
2 changes: 1 addition & 1 deletion src/sphinx_autodoc_typehints/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ def format_annotation(annotation: Any, config: Config) -> str: # noqa: C901 # t
role = "data"
args_format = f"\\[:py:data:`{prefix}typing.Union`\\[{{}}]]"
args = tuple(x for x in args if x is not type(None)) # noqa: E721
elif full_name == "typing.Callable" and args and args[0] is not ...:
elif full_name in ("typing.Callable", "collections.abc.Callable") and args and args[0] is not ...:
fmt = [format_annotation(arg, config) for arg in args]
formatted_args = f"\\[\\[{', '.join(fmt[:-1])}], {fmt[-1]}]"
elif full_name == "typing.Literal":
Expand Down
28 changes: 28 additions & 0 deletions tests/test_sphinx_autodoc_typehints.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,23 @@ def method(self: T) -> T:

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

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

AbcCallable = MyGenericAlias(collections.abc.Callable, (), special=True)
AbcCallable.__module__ = "collections.abc"


@pytest.mark.parametrize(
("annotation", "module", "class_name", "args"),
Expand All @@ -128,6 +145,13 @@ def method(self: T) -> T:
pytest.param(Callable, "typing", "Callable", (), id="Callable"),
pytest.param(Callable[..., str], "typing", "Callable", (..., str), id="Callable_returntype"),
pytest.param(Callable[[int, str], str], "typing", "Callable", (int, str, str), id="Callable_all_types"),
pytest.param(
AbcCallable[[int, str], str], # type: ignore[misc]
"collections.abc",
"Callable",
(int, str, str),
id="collections.abc.Callable_all_types",
),
pytest.param(Pattern, "typing", "Pattern", (), id="Pattern"),
pytest.param(Pattern[str], "typing", "Pattern", (str,), id="Pattern_parametrized"),
pytest.param(Match, "typing", "Match", (), id="Match"),
Expand Down Expand Up @@ -224,6 +248,10 @@ def test_parse_annotation(annotation: Any, module: str, class_name: str, args: t
":py:data:`~typing.Callable`\\[\\[:py:class:`~typing.TypeVar`\\(``T``)],"
" :py:class:`~typing.TypeVar`\\(``T``)]",
),
(
AbcCallable[[int, str], bool], # type: ignore[misc]
":py:class:`~collections.abc.Callable`\\[\\[:py:class:`int`, " ":py:class:`str`], :py:class:`bool`]",
),
(Pattern, ":py:class:`~typing.Pattern`"),
(Pattern[str], ":py:class:`~typing.Pattern`\\[:py:class:`str`]"),
(IO, ":py:class:`~typing.IO`"),
Expand Down

0 comments on commit abc497f

Please sign in to comment.