Style tweaks to `sphinx.util.typing` (#12282)

This commit is contained in:
Bénédikt Tran
2024-04-23 06:36:04 +02:00
committed by GitHub
parent 55ca37f684
commit b6948b8d74

View File

@@ -14,7 +14,8 @@ from docutils import nodes
from docutils.parsers.rst.states import Inliner from docutils.parsers.rst.states import Inliner
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Literal from collections.abc import Mapping
from typing import Final, Literal
from typing_extensions import TypeAlias from typing_extensions import TypeAlias
@@ -36,7 +37,7 @@ else:
UnionType = None UnionType = None
# classes that have an incorrect .__module__ attribute # classes that have an incorrect .__module__ attribute
_INVALID_BUILTIN_CLASSES = { _INVALID_BUILTIN_CLASSES: Final[Mapping[object, str]] = {
Context: 'contextvars.Context', # Context.__module__ == '_contextvars' Context: 'contextvars.Context', # Context.__module__ == '_contextvars'
ContextVar: 'contextvars.ContextVar', # ContextVar.__module__ == '_contextvars' ContextVar: 'contextvars.ContextVar', # ContextVar.__module__ == '_contextvars'
Token: 'contextvars.Token', # Token.__module__ == '_contextvars' Token: 'contextvars.Token', # Token.__module__ == '_contextvars'
@@ -83,8 +84,10 @@ NoneType = type(None)
PathMatcher = Callable[[str], bool] PathMatcher = Callable[[str], bool]
# common role functions # common role functions
RoleFunction = Callable[[str, str, str, int, Inliner, dict[str, Any], Sequence[str]], RoleFunction = Callable[
tuple[list[nodes.Node], list[nodes.system_message]]] [str, str, str, int, Inliner, dict[str, Any], Sequence[str]],
tuple[list[nodes.Node], list[nodes.system_message]],
]
# A option spec for directive # A option spec for directive
OptionSpec = dict[str, Callable[[str], Any]] OptionSpec = dict[str, Callable[[str], Any]]
@@ -127,7 +130,9 @@ if TYPE_CHECKING:
def get_type_hints( def get_type_hints(
obj: Any, globalns: dict[str, Any] | None = None, localns: dict[str, Any] | None = None, obj: Any,
globalns: dict[str, Any] | None = None,
localns: dict[str, Any] | None = None,
) -> dict[str, Any]: ) -> dict[str, Any]:
"""Return a dictionary containing type hints for a function, method, module or class """Return a dictionary containing type hints for a function, method, module or class
object. object.
@@ -201,13 +206,15 @@ def restify(cls: type | None, mode: _RestifyMode = 'fully-qualified-except-typin
elif ismock(cls): elif ismock(cls):
return f':py:class:`{modprefix}{cls.__module__}.{cls.__name__}`' return f':py:class:`{modprefix}{cls.__module__}.{cls.__name__}`'
elif is_invalid_builtin_class(cls): elif is_invalid_builtin_class(cls):
# The above predicate never raises TypeError but should not be
# evaluated before determining whether *cls* is a mocked object
# or not; instead of two try-except blocks, we keep it here.
return f':py:class:`{modprefix}{_INVALID_BUILTIN_CLASSES[cls]}`' return f':py:class:`{modprefix}{_INVALID_BUILTIN_CLASSES[cls]}`'
elif inspect.isNewType(cls): elif inspect.isNewType(cls):
if sys.version_info[:2] >= (3, 10): if sys.version_info[:2] >= (3, 10):
# newtypes have correct module info since Python 3.10+ # newtypes have correct module info since Python 3.10+
return f':py:class:`{modprefix}{cls.__module__}.{cls.__name__}`' return f':py:class:`{modprefix}{cls.__module__}.{cls.__name__}`'
else: return f':py:class:`{cls.__name__}`'
return f':py:class:`{cls.__name__}`'
elif UnionType and isinstance(cls, UnionType): elif UnionType and isinstance(cls, UnionType):
# Union types (PEP 585) retain their definition order when they # Union types (PEP 585) retain their definition order when they
# are printed natively and ``None``-like types are kept as is. # are printed natively and ``None``-like types are kept as is.
@@ -219,8 +226,7 @@ def restify(cls: type | None, mode: _RestifyMode = 'fully-qualified-except-typin
concatenated_args = ', '.join(restify(arg, mode) for arg in cls.__args__) concatenated_args = ', '.join(restify(arg, mode) for arg in cls.__args__)
return fr':py:class:`{cls.__name__}`\ [{concatenated_args}]' return fr':py:class:`{cls.__name__}`\ [{concatenated_args}]'
else: return f':py:class:`{cls.__name__}`'
return f':py:class:`{cls.__name__}`'
elif (inspect.isgenericalias(cls) elif (inspect.isgenericalias(cls)
and cls.__module__ == 'typing' and cls.__module__ == 'typing'
and cls.__origin__ is Union): # type: ignore[attr-defined] and cls.__origin__ is Union): # type: ignore[attr-defined]
@@ -241,10 +247,9 @@ def restify(cls: type | None, mode: _RestifyMode = 'fully-qualified-except-typin
elif all(is_system_TypeVar(a) for a in cls.__args__): elif all(is_system_TypeVar(a) for a in cls.__args__):
# Suppress arguments if all system defined TypeVars (ex. Dict[KT, VT]) # Suppress arguments if all system defined TypeVars (ex. Dict[KT, VT])
pass pass
elif (cls.__module__ == 'typing' elif cls.__module__ == 'typing' and cls._name == 'Callable': # type: ignore[attr-defined]
and cls._name == 'Callable'): # type: ignore[attr-defined]
args = ', '.join(restify(a, mode) for a in cls.__args__[:-1]) args = ', '.join(restify(a, mode) for a in cls.__args__[:-1])
text += fr"\ [[{args}], {restify(cls.__args__[-1], mode)}]" text += fr'\ [[{args}], {restify(cls.__args__[-1], mode)}]'
elif cls.__module__ == 'typing' and getattr(origin, '_name', None) == 'Literal': elif cls.__module__ == 'typing' and getattr(origin, '_name', None) == 'Literal':
args = ', '.join(_format_literal_arg_restify(a, mode=mode) args = ', '.join(_format_literal_arg_restify(a, mode=mode)
for a in cls.__args__) for a in cls.__args__)
@@ -327,45 +332,45 @@ def stringify_annotation(
else: else:
module_prefix = '' module_prefix = ''
annotation_qualname = getattr(annotation, '__qualname__', '') # The values below must be strings if the objects are well-formed.
annotation_module = getattr(annotation, '__module__', '') annotation_qualname: str = getattr(annotation, '__qualname__', '')
annotation_name = getattr(annotation, '__name__', '') annotation_module: str = getattr(annotation, '__module__', '')
annotation_name: str = getattr(annotation, '__name__', '')
annotation_module_is_typing = annotation_module == 'typing' annotation_module_is_typing = annotation_module == 'typing'
if isinstance(annotation, TypeVar): if isinstance(annotation, TypeVar):
if annotation_module_is_typing and mode in {'fully-qualified-except-typing', 'smart'}: if annotation_module_is_typing and mode in {'fully-qualified-except-typing', 'smart'}:
return annotation_name return annotation_name
else: return module_prefix + f'{annotation_module}.{annotation_name}'
return module_prefix + f'{annotation_module}.{annotation_name}'
elif isNewType(annotation): elif isNewType(annotation):
if sys.version_info[:2] >= (3, 10): if sys.version_info[:2] >= (3, 10):
# newtypes have correct module info since Python 3.10+ # newtypes have correct module info since Python 3.10+
return module_prefix + f'{annotation_module}.{annotation_name}' return module_prefix + f'{annotation_module}.{annotation_name}'
else: return annotation_name
return annotation_name
elif ismockmodule(annotation): elif ismockmodule(annotation):
return module_prefix + annotation_name return module_prefix + annotation_name
elif ismock(annotation): elif ismock(annotation):
return module_prefix + f'{annotation_module}.{annotation_name}' return module_prefix + f'{annotation_module}.{annotation_name}'
elif is_invalid_builtin_class(annotation): elif is_invalid_builtin_class(annotation):
return module_prefix + _INVALID_BUILTIN_CLASSES[annotation] return module_prefix + _INVALID_BUILTIN_CLASSES[annotation]
elif str(annotation).startswith('typing.Annotated'): # for py310+ elif str(annotation).startswith('typing.Annotated'): # for py39+
pass pass
elif annotation_module == 'builtins' and annotation_qualname: elif annotation_module == 'builtins' and annotation_qualname:
if (args := getattr(annotation, '__args__', None)) is not None: # PEP 585 generic args = getattr(annotation, '__args__', None)
if not args: # Empty tuple, list, ... if args is None:
return repr(annotation)
concatenated_args = ', '.join(stringify_annotation(arg, mode) for arg in args)
return f'{annotation_qualname}[{concatenated_args}]'
else:
return annotation_qualname return annotation_qualname
# PEP 585 generic
if not args: # Empty tuple, list, ...
return repr(annotation)
concatenated_args = ', '.join(stringify_annotation(arg, mode) for arg in args)
return f'{annotation_qualname}[{concatenated_args}]'
module_prefix = f'{annotation_module}.' module_prefix = f'{annotation_module}.'
annotation_forward_arg = getattr(annotation, '__forward_arg__', None) annotation_forward_arg: str | None = getattr(annotation, '__forward_arg__', None)
if annotation_qualname or (annotation_module_is_typing and not annotation_forward_arg): if annotation_qualname or (annotation_module_is_typing and not annotation_forward_arg):
if mode == 'smart': if mode == 'smart':
module_prefix = '~' + module_prefix module_prefix = f'~{module_prefix}'
if annotation_module_is_typing and mode == 'fully-qualified-except-typing': if annotation_module_is_typing and mode == 'fully-qualified-except-typing':
module_prefix = '' module_prefix = ''
else: else: