Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle types from types module #292

Merged
merged 2 commits into from
Jan 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
26 changes: 20 additions & 6 deletions src/sphinx_autodoc_typehints/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import re
import sys
import textwrap
import types
from ast import FunctionDef, Module, stmt
from typing import Any, AnyStr, Callable, ForwardRef, NewType, TypeVar, get_type_hints

Expand All @@ -21,8 +22,16 @@
_LOGGER = logging.getLogger(__name__)
_PYDATA_ANNOTATIONS = {"Any", "AnyStr", "Callable", "ClassVar", "Literal", "NoReturn", "Optional", "Tuple", "Union"}

# types has a bunch of things like ModuleType where ModuleType.__module__ is
# "builtins" and ModuleType.__name__ is "module", so we have to check for this.
_TYPES_DICT = {getattr(types, name): name for name in types.__all__}
# Prefer FunctionType to LambdaType (they are synonymous)
_TYPES_DICT[types.FunctionType] = "FunctionType"


def get_annotation_module(annotation: Any) -> str:
if annotation in _TYPES_DICT:
return "types"
if annotation is None:
return "builtins"
is_new_type = sys.version_info >= (3, 10) and isinstance(annotation, NewType)
Expand All @@ -35,17 +44,22 @@ def get_annotation_module(annotation: Any) -> str:
raise ValueError(f"Cannot determine the module of {annotation}")


def _is_newtype(annotation: Any) -> bool:
if sys.version_info < (3, 10):
return inspect.isfunction(annotation) and hasattr(annotation, "__supertype__")
else:
return isinstance(annotation, NewType)


def get_annotation_class_name(annotation: Any, module: str) -> str:
# Special cases
if annotation is None:
return "None"
elif annotation is Any:
return "Any"
elif annotation is AnyStr:
if annotation is AnyStr:
return "AnyStr"
elif (sys.version_info < (3, 10) and inspect.isfunction(annotation) and hasattr(annotation, "__supertype__")) or (
sys.version_info >= (3, 10) and isinstance(annotation, NewType)
):
if annotation in _TYPES_DICT:
return _TYPES_DICT[annotation]
if _is_newtype(annotation):
return "NewType"

if getattr(annotation, "__qualname__", None):
Expand Down
14 changes: 11 additions & 3 deletions tests/test_sphinx_autodoc_typehints.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import pathlib
import re
import sys
import types
import typing
from functools import cmp_to_key
from io import StringIO
Expand Down Expand Up @@ -134,6 +135,10 @@ def __getitem__(self, params):
[
pytest.param(str, "builtins", "str", (), id="str"),
pytest.param(None, "builtins", "None", (), id="None"),
pytest.param(ModuleType, "types", "ModuleType", (), id="ModuleType"),
pytest.param(FunctionType, "types", "FunctionType", (), id="FunctionType"),
pytest.param(types.CodeType, "types", "CodeType", (), id="CodeType"),
pytest.param(types.CoroutineType, "types", "CoroutineType", (), id="CoroutineType"),
pytest.param(Any, "typing", "Any", (), id="Any"),
pytest.param(AnyStr, "typing", "AnyStr", (), id="AnyStr"),
pytest.param(Dict, "typing", "Dict", (), id="Dict"),
Expand Down Expand Up @@ -170,9 +175,10 @@ def __getitem__(self, params):
],
)
def test_parse_annotation(annotation: Any, module: str, class_name: str, args: tuple[Any, ...]) -> None:
assert get_annotation_module(annotation) == module
assert get_annotation_class_name(annotation, module) == class_name
assert get_annotation_args(annotation, module, class_name) == args
got_mod = get_annotation_module(annotation)
got_cls = get_annotation_class_name(annotation, module)
got_args = get_annotation_args(annotation, module, class_name)
assert (got_mod, got_cls, got_args) == (module, class_name, args)


@pytest.mark.parametrize(
Expand All @@ -181,6 +187,8 @@ def test_parse_annotation(annotation: Any, module: str, class_name: str, args: t
(str, ":py:class:`str`"),
(int, ":py:class:`int`"),
(StringIO, ":py:class:`~io.StringIO`"),
(FunctionType, ":py:class:`~types.FunctionType`"),
(ModuleType, ":py:class:`~types.ModuleType`"),
(type(None), ":py:obj:`None`"),
(type, ":py:class:`type`"),
(collections.abc.Callable, ":py:class:`~collections.abc.Callable`"),
Expand Down
3 changes: 3 additions & 0 deletions whitelist.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ autouse
backfill
conf
contravariant
Coroutine
cpython
csv
dedent
Expand Down Expand Up @@ -31,7 +32,9 @@ iterdir
kwonlyargs
libs
metaclass
ModuleType
multiline
newtype
nptyping
param
parametrized
Expand Down