summaryrefslogtreecommitdiff
path: root/sphinx/util/typing.py
diff options
context:
space:
mode:
Diffstat (limited to 'sphinx/util/typing.py')
-rw-r--r--sphinx/util/typing.py152
1 files changed, 84 insertions, 68 deletions
diff --git a/sphinx/util/typing.py b/sphinx/util/typing.py
index 53f32ed46..282230e0c 100644
--- a/sphinx/util/typing.py
+++ b/sphinx/util/typing.py
@@ -11,6 +11,8 @@ from typing import Any, Callable, Dict, ForwardRef, List, Tuple, TypeVar, Union
from docutils import nodes
from docutils.parsers.rst.states import Inliner
+from sphinx.deprecation import RemovedInSphinx80Warning, deprecated_alias
+
try:
from types import UnionType # type: ignore # python 3.10 or above
except ImportError:
@@ -205,9 +207,14 @@ def restify(cls: type | None, mode: str = 'fully-qualified-except-typing') -> st
return inspect.object_description(cls)
-def stringify(annotation: Any, mode: str = 'fully-qualified-except-typing') -> str:
+def stringify_annotation(
+ annotation: Any,
+ /,
+ mode: str = 'fully-qualified-except-typing',
+) -> str:
"""Stringify type annotation object.
+ :param annotation: The annotation to stringified.
:param mode: Specify a method how annotations will be stringified.
'fully-qualified-except-typing'
@@ -219,12 +226,20 @@ def stringify(annotation: Any, mode: str = 'fully-qualified-except-typing') -> s
Show the module name and qualified name of the annotation.
"""
from sphinx.ext.autodoc.mock import ismock, ismockmodule # lazy loading
- from sphinx.util import inspect # lazy loading
+ from sphinx.util.inspect import isNewType # lazy loading
+
+ if mode not in {'fully-qualified-except-typing', 'fully-qualified', 'smart'}:
+ raise ValueError("'mode' must be one of 'fully-qualified-except-typing', "
+ f"'fully-qualified', or 'smart'; got {mode!r}.")
if mode == 'smart':
- modprefix = '~'
+ module_prefix = '~'
else:
- modprefix = ''
+ module_prefix = ''
+
+ annotation_qualname = getattr(annotation, '__qualname__', '')
+ annotation_module = getattr(annotation, '__module__', '')
+ annotation_name = getattr(annotation, '__name__', '')
if isinstance(annotation, str):
if annotation.startswith("'") and annotation.endswith("'"):
@@ -233,104 +248,105 @@ def stringify(annotation: Any, mode: str = 'fully-qualified-except-typing') -> s
else:
return annotation
elif isinstance(annotation, TypeVar):
- if (annotation.__module__ == 'typing' and
- mode in ('fully-qualified-except-typing', 'smart')):
- return annotation.__name__
+ if (annotation_module == 'typing'
+ and mode in {'fully-qualified-except-typing', 'smart'}):
+ return annotation_name
else:
- return modprefix + '.'.join([annotation.__module__, annotation.__name__])
- elif inspect.isNewType(annotation):
+ return module_prefix + f'{annotation_module}.{annotation_name}'
+ elif isNewType(annotation):
if sys.version_info[:2] >= (3, 10):
# newtypes have correct module info since Python 3.10+
- return modprefix + f'{annotation.__module__}.{annotation.__name__}'
+ return module_prefix + f'{annotation_module}.{annotation_name}'
else:
- return annotation.__name__
+ return annotation_name
elif not annotation:
return repr(annotation)
elif annotation is NoneType:
return 'None'
elif ismockmodule(annotation):
- return modprefix + annotation.__name__
+ return module_prefix + annotation_name
elif ismock(annotation):
- return modprefix + f'{annotation.__module__}.{annotation.__name__}'
+ return module_prefix + f'{annotation_module}.{annotation_name}'
elif is_invalid_builtin_class(annotation):
- return modprefix + INVALID_BUILTIN_CLASSES[annotation]
+ return module_prefix + INVALID_BUILTIN_CLASSES[annotation]
elif str(annotation).startswith('typing.Annotated'): # for py310+
pass
- elif (getattr(annotation, '__module__', None) == 'builtins' and
- getattr(annotation, '__qualname__', None)):
+ elif annotation_module == 'builtins' and annotation_qualname:
if hasattr(annotation, '__args__'): # PEP 585 generic
return repr(annotation)
else:
- return annotation.__qualname__
+ return annotation_qualname
elif annotation is Ellipsis:
return '...'
- module = getattr(annotation, '__module__', None)
- modprefix = ''
- if module == 'typing' and getattr(annotation, '__forward_arg__', None):
- qualname = annotation.__forward_arg__
- elif module == 'typing':
- if getattr(annotation, '_name', None):
- qualname = annotation._name
- elif getattr(annotation, '__qualname__', None):
- qualname = annotation.__qualname__
- else:
- qualname = stringify(annotation.__origin__).replace('typing.', '') # ex. Union
-
+ module_prefix = ''
+ annotation_forward_arg = getattr(annotation, '__forward_arg__', None)
+ if (annotation_qualname
+ or (annotation_module == 'typing' and not annotation_forward_arg)):
if mode == 'smart':
- modprefix = '~%s.' % module
+ module_prefix = f'~{annotation_module}.'
elif mode == 'fully-qualified':
- modprefix = '%s.' % module
- elif hasattr(annotation, '__qualname__'):
- if mode == 'smart':
- modprefix = '~%s.' % module
+ module_prefix = f'{annotation_module}.'
+ elif annotation_module != 'typing' and mode == 'fully-qualified-except-typing':
+ module_prefix = f'{annotation_module}.'
+
+ if annotation_module == 'typing':
+ if annotation_forward_arg:
+ # handle ForwardRefs
+ qualname = annotation_forward_arg
else:
- modprefix = '%s.' % module
- qualname = annotation.__qualname__
+ _name = getattr(annotation, '_name', '')
+ if _name:
+ qualname = _name
+ elif annotation_qualname:
+ qualname = annotation_qualname
+ else:
+ qualname = stringify_annotation(
+ annotation.__origin__, 'fully-qualified-except-typing'
+ ).replace('typing.', '') # ex. Union
+ elif annotation_qualname:
+ qualname = annotation_qualname
elif hasattr(annotation, '__origin__'):
# instantiated generic provided by a user
- qualname = stringify(annotation.__origin__, mode)
- elif UnionType and isinstance(annotation, UnionType): # types.Union (for py3.10+)
- qualname = 'types.Union'
+ qualname = stringify_annotation(annotation.__origin__, mode)
+ elif UnionType and isinstance(annotation, UnionType): # types.UnionType (for py3.10+)
+ qualname = 'types.UnionType'
else:
# we weren't able to extract the base type, appending arguments would
# only make them appear twice
return repr(annotation)
- if getattr(annotation, '__args__', None):
- if not isinstance(annotation.__args__, (list, tuple)):
+ annotation_args = getattr(annotation, '__args__', None)
+ if annotation_args:
+ if not isinstance(annotation_args, (list, tuple)):
# broken __args__ found
pass
- elif qualname in ('Optional', 'Union'):
- if len(annotation.__args__) > 1 and annotation.__args__[-1] is NoneType:
- if len(annotation.__args__) > 2:
- args = ', '.join(stringify(a, mode) for a in annotation.__args__[:-1])
- return f'{modprefix}Optional[{modprefix}Union[{args}]]'
- else:
- return f'{modprefix}Optional[{stringify(annotation.__args__[0], mode)}]'
- else:
- args = ', '.join(stringify(a, mode) for a in annotation.__args__)
- return f'{modprefix}Union[{args}]'
- elif qualname == 'types.Union':
- if len(annotation.__args__) > 1 and None in annotation.__args__:
- args = ' | '.join(stringify(a) for a in annotation.__args__ if a)
- return f'{modprefix}Optional[{args}]'
- else:
- return ' | '.join(stringify(a) for a in annotation.__args__)
+ elif qualname in {'Optional', 'Union', 'types.UnionType'}:
+ return ' | '.join(stringify_annotation(a, mode) for a in annotation_args)
elif qualname == 'Callable':
- args = ', '.join(stringify(a, mode) for a in annotation.__args__[:-1])
- returns = stringify(annotation.__args__[-1], mode)
- return f'{modprefix}{qualname}[[{args}], {returns}]'
+ args = ', '.join(stringify_annotation(a, mode) for a in annotation_args[:-1])
+ returns = stringify_annotation(annotation_args[-1], mode)
+ return f'{module_prefix}Callable[[{args}], {returns}]'
elif qualname == 'Literal':
- args = ', '.join(repr(a) for a in annotation.__args__)
- return f'{modprefix}{qualname}[{args}]'
+ args = ', '.join(repr(a) for a in annotation_args)
+ return f'{module_prefix}Literal[{args}]'
elif str(annotation).startswith('typing.Annotated'): # for py39+
- return stringify(annotation.__args__[0], mode)
- elif all(is_system_TypeVar(a) for a in annotation.__args__):
+ return stringify_annotation(annotation_args[0], mode)
+ elif all(is_system_TypeVar(a) for a in annotation_args):
# Suppress arguments if all system defined TypeVars (ex. Dict[KT, VT])
- return modprefix + qualname
+ return module_prefix + qualname
else:
- args = ', '.join(stringify(a, mode) for a in annotation.__args__)
- return f'{modprefix}{qualname}[{args}]'
+ args = ', '.join(stringify_annotation(a, mode) for a in annotation_args)
+ return f'{module_prefix}{qualname}[{args}]'
+
+ return module_prefix + qualname
+
- return modprefix + qualname
+deprecated_alias(__name__,
+ {
+ 'stringify': stringify_annotation,
+ },
+ RemovedInSphinx80Warning,
+ {
+ 'stringify': 'sphinx.util.typing.stringify_annotation',
+ })