mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2025-02-25 18:55:22 -06:00
Add mode
parameter to sphinx.util.typing:restify()
To make the typehints in "Bases" field simple, this adds a new parameter `mode` to sphinx.util.typing:restify() to suppress the leading module name from typehints in "Bases" field.
This commit is contained in:
@@ -105,10 +105,24 @@ def is_system_TypeVar(typ: Any) -> bool:
|
||||
return modname == 'typing' and isinstance(typ, TypeVar)
|
||||
|
||||
|
||||
def restify(cls: Optional[Type]) -> str:
|
||||
"""Convert python class to a reST reference."""
|
||||
def restify(cls: Optional[Type], mode: str = 'fully-qualified-except-typing') -> str:
|
||||
"""Convert python class to a reST reference.
|
||||
|
||||
:param mode: Specify a method how annotations will be stringified.
|
||||
|
||||
'fully-qualified-except-typing'
|
||||
Show the module name and qualified name of the annotation except
|
||||
the "typing" module.
|
||||
'smart'
|
||||
Show the name of the annotation.
|
||||
"""
|
||||
from sphinx.util import inspect # lazy loading
|
||||
|
||||
if mode == 'smart':
|
||||
modprefix = '~'
|
||||
else:
|
||||
modprefix = ''
|
||||
|
||||
try:
|
||||
if cls is None or cls is NoneType:
|
||||
return ':py:obj:`None`'
|
||||
@@ -117,63 +131,67 @@ def restify(cls: Optional[Type]) -> str:
|
||||
elif isinstance(cls, str):
|
||||
return cls
|
||||
elif cls in INVALID_BUILTIN_CLASSES:
|
||||
return ':py:class:`%s`' % INVALID_BUILTIN_CLASSES[cls]
|
||||
return ':py:class:`%s%s`' % (modprefix, INVALID_BUILTIN_CLASSES[cls])
|
||||
elif inspect.isNewType(cls):
|
||||
if sys.version_info > (3, 10):
|
||||
# newtypes have correct module info since Python 3.10+
|
||||
print(cls, type(cls), dir(cls))
|
||||
return ':py:class:`%s.%s`' % (cls.__module__, cls.__name__)
|
||||
return ':py:class:`%s%s.%s`' % (modprefix, cls.__module__, cls.__name__)
|
||||
else:
|
||||
return ':py:class:`%s`' % cls.__name__
|
||||
elif UnionType and isinstance(cls, UnionType):
|
||||
if len(cls.__args__) > 1 and None in cls.__args__:
|
||||
args = ' | '.join(restify(a) for a in cls.__args__ if a)
|
||||
args = ' | '.join(restify(a, mode) for a in cls.__args__ if a)
|
||||
return 'Optional[%s]' % args
|
||||
else:
|
||||
return ' | '.join(restify(a) for a in cls.__args__)
|
||||
return ' | '.join(restify(a, mode) for a in cls.__args__)
|
||||
elif cls.__module__ in ('__builtin__', 'builtins'):
|
||||
if hasattr(cls, '__args__'):
|
||||
return ':py:class:`%s`\\ [%s]' % (
|
||||
cls.__name__,
|
||||
', '.join(restify(arg) for arg in cls.__args__),
|
||||
', '.join(restify(arg, mode) for arg in cls.__args__),
|
||||
)
|
||||
else:
|
||||
return ':py:class:`%s`' % cls.__name__
|
||||
else:
|
||||
if sys.version_info >= (3, 7): # py37+
|
||||
return _restify_py37(cls)
|
||||
return _restify_py37(cls, mode)
|
||||
else:
|
||||
return _restify_py36(cls)
|
||||
return _restify_py36(cls, mode)
|
||||
except (AttributeError, TypeError):
|
||||
return inspect.object_description(cls)
|
||||
|
||||
|
||||
def _restify_py37(cls: Optional[Type]) -> str:
|
||||
def _restify_py37(cls: Optional[Type], mode: str = 'fully-qualified-except-typing') -> str:
|
||||
"""Convert python class to a reST reference."""
|
||||
from sphinx.util import inspect # lazy loading
|
||||
|
||||
if mode == 'smart':
|
||||
modprefix = '~'
|
||||
else:
|
||||
modprefix = ''
|
||||
|
||||
if (inspect.isgenericalias(cls) and
|
||||
cls.__module__ == 'typing' and cls.__origin__ is Union):
|
||||
# Union
|
||||
if len(cls.__args__) > 1 and cls.__args__[-1] is NoneType:
|
||||
if len(cls.__args__) > 2:
|
||||
args = ', '.join(restify(a) for a in cls.__args__[:-1])
|
||||
args = ', '.join(restify(a, mode) for a in cls.__args__[:-1])
|
||||
return ':py:obj:`~typing.Optional`\\ [:obj:`~typing.Union`\\ [%s]]' % args
|
||||
else:
|
||||
return ':py:obj:`~typing.Optional`\\ [%s]' % restify(cls.__args__[0])
|
||||
return ':py:obj:`~typing.Optional`\\ [%s]' % restify(cls.__args__[0], mode)
|
||||
else:
|
||||
args = ', '.join(restify(a) for a in cls.__args__)
|
||||
args = ', '.join(restify(a, mode) for a in cls.__args__)
|
||||
return ':py:obj:`~typing.Union`\\ [%s]' % args
|
||||
elif inspect.isgenericalias(cls):
|
||||
if isinstance(cls.__origin__, typing._SpecialForm):
|
||||
text = restify(cls.__origin__) # type: ignore
|
||||
text = restify(cls.__origin__, mode) # type: ignore
|
||||
elif getattr(cls, '_name', None):
|
||||
if cls.__module__ == 'typing':
|
||||
text = ':py:class:`~%s.%s`' % (cls.__module__, cls._name)
|
||||
else:
|
||||
text = ':py:class:`%s.%s`' % (cls.__module__, cls._name)
|
||||
text = ':py:class:`%s%s.%s`' % (modprefix, cls.__module__, cls._name)
|
||||
else:
|
||||
text = restify(cls.__origin__)
|
||||
text = restify(cls.__origin__, mode)
|
||||
|
||||
origin = getattr(cls, '__origin__', None)
|
||||
if not hasattr(cls, '__args__'):
|
||||
@@ -182,12 +200,12 @@ def _restify_py37(cls: Optional[Type]) -> str:
|
||||
# Suppress arguments if all system defined TypeVars (ex. Dict[KT, VT])
|
||||
pass
|
||||
elif cls.__module__ == 'typing' and cls._name == 'Callable':
|
||||
args = ', '.join(restify(a) for a in cls.__args__[:-1])
|
||||
text += r"\ [[%s], %s]" % (args, restify(cls.__args__[-1]))
|
||||
args = ', '.join(restify(a, mode) for a in cls.__args__[:-1])
|
||||
text += r"\ [[%s], %s]" % (args, restify(cls.__args__[-1], mode))
|
||||
elif cls.__module__ == 'typing' and getattr(origin, '_name', None) == 'Literal':
|
||||
text += r"\ [%s]" % ', '.join(repr(a) for a in cls.__args__)
|
||||
elif cls.__args__:
|
||||
text += r"\ [%s]" % ", ".join(restify(a) for a in cls.__args__)
|
||||
text += r"\ [%s]" % ", ".join(restify(a, mode) for a in cls.__args__)
|
||||
|
||||
return text
|
||||
elif isinstance(cls, typing._SpecialForm):
|
||||
@@ -196,7 +214,7 @@ def _restify_py37(cls: Optional[Type]) -> str:
|
||||
if cls.__module__ == 'typing':
|
||||
return ':py:class:`~%s.%s`' % (cls.__module__, cls.__qualname__)
|
||||
else:
|
||||
return ':py:class:`%s.%s`' % (cls.__module__, cls.__qualname__)
|
||||
return ':py:class:`%s%s.%s`' % (modprefix, cls.__module__, cls.__qualname__)
|
||||
elif isinstance(cls, ForwardRef):
|
||||
return ':py:class:`%s`' % cls.__forward_arg__
|
||||
else:
|
||||
@@ -204,10 +222,15 @@ def _restify_py37(cls: Optional[Type]) -> str:
|
||||
if cls.__module__ == 'typing':
|
||||
return ':py:obj:`~%s.%s`' % (cls.__module__, cls.__name__)
|
||||
else:
|
||||
return ':py:obj:`%s.%s`' % (cls.__module__, cls.__name__)
|
||||
return ':py:obj:`%s%s.%s`' % (modprefix, cls.__module__, cls.__name__)
|
||||
|
||||
|
||||
def _restify_py36(cls: Optional[Type]) -> str:
|
||||
def _restify_py36(cls: Optional[Type], mode: str = 'fully-qualified-except-typing') -> str:
|
||||
if mode == 'smart':
|
||||
modprefix = '~'
|
||||
else:
|
||||
modprefix = ''
|
||||
|
||||
module = getattr(cls, '__module__', None)
|
||||
if module == 'typing':
|
||||
if getattr(cls, '_name', None):
|
||||
@@ -221,7 +244,7 @@ def _restify_py36(cls: Optional[Type]) -> str:
|
||||
else:
|
||||
qualname = repr(cls).replace('typing.', '')
|
||||
elif hasattr(cls, '__qualname__'):
|
||||
qualname = '%s.%s' % (module, cls.__qualname__)
|
||||
qualname = '%s%s.%s' % (modprefix, module, cls.__qualname__)
|
||||
else:
|
||||
qualname = repr(cls)
|
||||
|
||||
@@ -230,11 +253,11 @@ def _restify_py36(cls: Optional[Type]) -> str:
|
||||
if module == 'typing':
|
||||
reftext = ':py:class:`~typing.%s`' % qualname
|
||||
else:
|
||||
reftext = ':py:class:`%s`' % qualname
|
||||
reftext = ':py:class:`%s%s`' % (modprefix, qualname)
|
||||
|
||||
params = cls.__args__
|
||||
if params:
|
||||
param_str = ', '.join(restify(p) for p in params)
|
||||
param_str = ', '.join(restify(p, mode) for p in params)
|
||||
return reftext + '\\ [%s]' % param_str
|
||||
else:
|
||||
return reftext
|
||||
@@ -242,19 +265,19 @@ def _restify_py36(cls: Optional[Type]) -> str:
|
||||
if module == 'typing':
|
||||
reftext = ':py:class:`~typing.%s`' % qualname
|
||||
else:
|
||||
reftext = ':py:class:`%s`' % qualname
|
||||
reftext = ':py:class:`%s%s`' % (modprefix, qualname)
|
||||
|
||||
if cls.__args__ is None or len(cls.__args__) <= 2:
|
||||
params = cls.__args__
|
||||
elif cls.__origin__ == Generator:
|
||||
params = cls.__args__
|
||||
else: # typing.Callable
|
||||
args = ', '.join(restify(arg) for arg in cls.__args__[:-1])
|
||||
result = restify(cls.__args__[-1])
|
||||
args = ', '.join(restify(arg, mode) for arg in cls.__args__[:-1])
|
||||
result = restify(cls.__args__[-1], mode)
|
||||
return reftext + '\\ [[%s], %s]' % (args, result)
|
||||
|
||||
if params:
|
||||
param_str = ', '.join(restify(p) for p in params)
|
||||
param_str = ', '.join(restify(p, mode) for p in params)
|
||||
return reftext + '\\ [%s]' % (param_str)
|
||||
else:
|
||||
return reftext
|
||||
@@ -264,13 +287,13 @@ def _restify_py36(cls: Optional[Type]) -> str:
|
||||
if params is not None:
|
||||
if len(params) > 1 and params[-1] is NoneType:
|
||||
if len(params) > 2:
|
||||
param_str = ", ".join(restify(p) for p in params[:-1])
|
||||
param_str = ", ".join(restify(p, mode) for p in params[:-1])
|
||||
return (':py:obj:`~typing.Optional`\\ '
|
||||
'[:py:obj:`~typing.Union`\\ [%s]]' % param_str)
|
||||
else:
|
||||
return ':py:obj:`~typing.Optional`\\ [%s]' % restify(params[0])
|
||||
return ':py:obj:`~typing.Optional`\\ [%s]' % restify(params[0], mode)
|
||||
else:
|
||||
param_str = ', '.join(restify(p) for p in params)
|
||||
param_str = ', '.join(restify(p, mode) for p in params)
|
||||
return ':py:obj:`~typing.Union`\\ [%s]' % param_str
|
||||
else:
|
||||
return ':py:obj:`Union`'
|
||||
@@ -278,25 +301,25 @@ def _restify_py36(cls: Optional[Type]) -> str:
|
||||
if cls.__module__ == 'typing':
|
||||
return ':py:class:`~%s.%s`' % (cls.__module__, cls.__qualname__)
|
||||
else:
|
||||
return ':py:class:`%s.%s`' % (cls.__module__, cls.__qualname__)
|
||||
return ':py:class:`%s%s.%s`' % (modprefix, cls.__module__, cls.__qualname__)
|
||||
elif hasattr(cls, '_name'):
|
||||
# SpecialForm
|
||||
if cls.__module__ == 'typing':
|
||||
return ':py:obj:`~%s.%s`' % (cls.__module__, cls._name)
|
||||
else:
|
||||
return ':py:obj:`%s.%s`' % (cls.__module__, cls._name)
|
||||
return ':py:obj:`%s%s.%s`' % (modprefix, cls.__module__, cls._name)
|
||||
elif hasattr(cls, '__name__'):
|
||||
# not a class (ex. TypeVar)
|
||||
if cls.__module__ == 'typing':
|
||||
return ':py:obj:`~%s.%s`' % (cls.__module__, cls.__name__)
|
||||
else:
|
||||
return ':py:obj:`%s.%s`' % (cls.__module__, cls.__name__)
|
||||
return ':py:obj:`%s%s.%s`' % (modprefix, cls.__module__, cls.__name__)
|
||||
else:
|
||||
# others (ex. Any)
|
||||
if cls.__module__ == 'typing':
|
||||
return ':py:obj:`~%s.%s`' % (cls.__module__, qualname)
|
||||
else:
|
||||
return ':py:obj:`%s.%s`' % (cls.__module__, qualname)
|
||||
return ':py:obj:`%s%s.%s`' % (modprefix, cls.__module__, qualname)
|
||||
|
||||
|
||||
def stringify(annotation: Any, mode: str = 'fully-qualified-except-typing') -> str:
|
||||
|
@@ -43,13 +43,28 @@ class BrokenType:
|
||||
|
||||
def test_restify():
|
||||
assert restify(int) == ":py:class:`int`"
|
||||
assert restify(int, "smart") == ":py:class:`int`"
|
||||
|
||||
assert restify(str) == ":py:class:`str`"
|
||||
assert restify(str, "smart") == ":py:class:`str`"
|
||||
|
||||
assert restify(None) == ":py:obj:`None`"
|
||||
assert restify(None, "smart") == ":py:obj:`None`"
|
||||
|
||||
assert restify(Integral) == ":py:class:`numbers.Integral`"
|
||||
assert restify(Integral, "smart") == ":py:class:`~numbers.Integral`"
|
||||
|
||||
assert restify(Struct) == ":py:class:`struct.Struct`"
|
||||
assert restify(Struct, "smart") == ":py:class:`~struct.Struct`"
|
||||
|
||||
assert restify(TracebackType) == ":py:class:`types.TracebackType`"
|
||||
assert restify(TracebackType, "smart") == ":py:class:`~types.TracebackType`"
|
||||
|
||||
assert restify(Any) == ":py:obj:`~typing.Any`"
|
||||
assert restify(Any, "smart") == ":py:obj:`~typing.Any`"
|
||||
|
||||
assert restify('str') == "str"
|
||||
assert restify('str', "smart") == "str"
|
||||
|
||||
|
||||
def test_restify_type_hints_containers():
|
||||
@@ -99,13 +114,24 @@ def test_restify_type_hints_Union():
|
||||
if sys.version_info >= (3, 7):
|
||||
assert restify(Union[int, Integral]) == (":py:obj:`~typing.Union`\\ "
|
||||
"[:py:class:`int`, :py:class:`numbers.Integral`]")
|
||||
assert restify(Union[int, Integral], "smart") == (":py:obj:`~typing.Union`\\ "
|
||||
"[:py:class:`int`,"
|
||||
" :py:class:`~numbers.Integral`]")
|
||||
|
||||
assert (restify(Union[MyClass1, MyClass2]) ==
|
||||
(":py:obj:`~typing.Union`\\ "
|
||||
"[:py:class:`tests.test_util_typing.MyClass1`, "
|
||||
":py:class:`tests.test_util_typing.<MyClass2>`]"))
|
||||
assert (restify(Union[MyClass1, MyClass2], "smart") ==
|
||||
(":py:obj:`~typing.Union`\\ "
|
||||
"[:py:class:`~tests.test_util_typing.MyClass1`,"
|
||||
" :py:class:`~tests.test_util_typing.<MyClass2>`]"))
|
||||
else:
|
||||
assert restify(Union[int, Integral]) == ":py:class:`numbers.Integral`"
|
||||
assert restify(Union[int, Integral], "smart") == ":py:class:`~numbers.Integral`"
|
||||
|
||||
assert restify(Union[MyClass1, MyClass2]) == ":py:class:`tests.test_util_typing.MyClass1`"
|
||||
assert restify(Union[MyClass1, MyClass2], "smart") == ":py:class:`~tests.test_util_typing.MyClass1`"
|
||||
|
||||
|
||||
@pytest.mark.skipif(sys.version_info < (3, 7), reason='python 3.7+ is required.')
|
||||
@@ -115,19 +141,31 @@ def test_restify_type_hints_typevars():
|
||||
T_contra = TypeVar('T_contra', contravariant=True)
|
||||
|
||||
assert restify(T) == ":py:obj:`tests.test_util_typing.T`"
|
||||
assert restify(T, "smart") == ":py:obj:`~tests.test_util_typing.T`"
|
||||
|
||||
assert restify(T_co) == ":py:obj:`tests.test_util_typing.T_co`"
|
||||
assert restify(T_co, "smart") == ":py:obj:`~tests.test_util_typing.T_co`"
|
||||
|
||||
assert restify(T_contra) == ":py:obj:`tests.test_util_typing.T_contra`"
|
||||
assert restify(T_contra, "smart") == ":py:obj:`~tests.test_util_typing.T_contra`"
|
||||
|
||||
assert restify(List[T]) == ":py:class:`~typing.List`\\ [:py:obj:`tests.test_util_typing.T`]"
|
||||
assert restify(List[T], "smart") == ":py:class:`~typing.List`\\ [:py:obj:`~tests.test_util_typing.T`]"
|
||||
|
||||
if sys.version_info >= (3, 10):
|
||||
assert restify(MyInt) == ":py:class:`tests.test_util_typing.MyInt`"
|
||||
assert restify(MyInt, "smart") == ":py:class:`~tests.test_util_typing.MyInt`"
|
||||
else:
|
||||
assert restify(MyInt) == ":py:class:`MyInt`"
|
||||
assert restify(MyInt, "smart") == ":py:class:`MyInt`"
|
||||
|
||||
|
||||
def test_restify_type_hints_custom_class():
|
||||
assert restify(MyClass1) == ":py:class:`tests.test_util_typing.MyClass1`"
|
||||
assert restify(MyClass1, "smart") == ":py:class:`~tests.test_util_typing.MyClass1`"
|
||||
|
||||
assert restify(MyClass2) == ":py:class:`tests.test_util_typing.<MyClass2>`"
|
||||
assert restify(MyClass2, "smart") == ":py:class:`~tests.test_util_typing.<MyClass2>`"
|
||||
|
||||
|
||||
def test_restify_type_hints_alias():
|
||||
@@ -169,12 +207,14 @@ def test_restify_type_union_operator():
|
||||
|
||||
def test_restify_broken_type_hints():
|
||||
assert restify(BrokenType) == ':py:class:`tests.test_util_typing.BrokenType`'
|
||||
assert restify(BrokenType, "smart") == ':py:class:`~tests.test_util_typing.BrokenType`'
|
||||
|
||||
|
||||
def test_restify_mock():
|
||||
with mock(['unknown']):
|
||||
import unknown
|
||||
assert restify(unknown.secret.Class) == ':py:class:`unknown.secret.Class`'
|
||||
assert restify(unknown.secret.Class, "smart") == ':py:class:`~unknown.secret.Class`'
|
||||
|
||||
|
||||
def test_stringify():
|
||||
|
Reference in New Issue
Block a user