diff options
-rw-r--r-- | sphinx/ext/autodoc/__init__.py | 31 | ||||
-rw-r--r-- | sphinx/util/typing.py | 97 | ||||
-rw-r--r-- | tests/roots/test-ext-autodoc/target/genericalias.py | 3 | ||||
-rw-r--r-- | tests/roots/test-ext-autodoc/target/typevar.py | 5 | ||||
-rw-r--r-- | tests/test_ext_autodoc.py | 19 | ||||
-rw-r--r-- | tests/test_ext_autodoc_autoattribute.py | 2 | ||||
-rw-r--r-- | tests/test_ext_autodoc_autodata.py | 2 | ||||
-rw-r--r-- | tests/test_ext_autodoc_configs.py | 56 | ||||
-rw-r--r-- | tests/test_util_typing.py | 40 |
9 files changed, 207 insertions, 48 deletions
diff --git a/sphinx/ext/autodoc/__init__.py b/sphinx/ext/autodoc/__init__.py index 920e2220c..8a86f05b1 100644 --- a/sphinx/ext/autodoc/__init__.py +++ b/sphinx/ext/autodoc/__init__.py @@ -1676,7 +1676,11 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type: self.env.events.emit('autodoc-process-bases', self.fullname, self.object, self.options, bases) - base_classes = [restify(cls) for cls in bases] + if self.config.autodoc_typehints_format == "short": + base_classes = [restify(cls, "smart") for cls in bases] + else: + base_classes = [restify(cls) for cls in bases] + sourcename = self.get_sourcename() self.add_line('', sourcename) self.add_line(' ' + _('Bases: %s') % ', '.join(base_classes), sourcename) @@ -1773,7 +1777,11 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type: if self.doc_as_attr and not self.get_variable_comment(): try: - more_content = StringList([_('alias of %s') % restify(self.object)], source='') + if self.config.autodoc_typehints_format == "short": + alias = restify(self.object, "smart") + else: + alias = restify(self.object) + more_content = StringList([_('alias of %s') % alias], source='') except AttributeError: pass # Invalid class object is passed. @@ -1846,7 +1854,12 @@ class GenericAliasMixin(DataDocumenterMixinBase): def update_content(self, more_content: StringList) -> None: if inspect.isgenericalias(self.object): - more_content.append(_('alias of %s') % restify(self.object), '') + if self.config.autodoc_typehints_format == "short": + alias = restify(self.object, "smart") + else: + alias = restify(self.object) + + more_content.append(_('alias of %s') % alias, '') more_content.append('', '') super().update_content(more_content) @@ -1864,7 +1877,11 @@ class NewTypeMixin(DataDocumenterMixinBase): def update_content(self, more_content: StringList) -> None: if inspect.isNewType(self.object): - supertype = restify(self.object.__supertype__) + if self.config.autodoc_typehints_format == "short": + supertype = restify(self.object.__supertype__, "smart") + else: + supertype = restify(self.object.__supertype__) + more_content.append(_('alias of %s') % supertype, '') more_content.append('', '') @@ -1901,7 +1918,11 @@ class TypeVarMixin(DataDocumenterMixinBase): for constraint in self.object.__constraints__: attrs.append(stringify_typehint(constraint)) if self.object.__bound__: - attrs.append(r"bound=\ " + restify(self.object.__bound__)) + if self.config.autodoc_typehints_format == "short": + bound = restify(self.object.__bound__, "smart") + else: + bound = restify(self.object.__bound__) + attrs.append(r"bound=\ " + bound) if self.object.__covariant__: attrs.append("covariant=True") if self.object.__contravariant__: diff --git a/sphinx/util/typing.py b/sphinx/util/typing.py index 68b305cff..d9b63e046 100644 --- a/sphinx/util/typing.py +++ b/sphinx/util/typing.py @@ -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], mode: str = 'fully-qualified-except-typing') -> str: + if mode == 'smart': + modprefix = '~' + else: + modprefix = '' -def _restify_py36(cls: Optional[Type]) -> str: 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: diff --git a/tests/roots/test-ext-autodoc/target/genericalias.py b/tests/roots/test-ext-autodoc/target/genericalias.py index 9909efca1..3856e034d 100644 --- a/tests/roots/test-ext-autodoc/target/genericalias.py +++ b/tests/roots/test-ext-autodoc/target/genericalias.py @@ -9,3 +9,6 @@ C = Callable[[int], None] # a generic alias not having a doccomment class Class: #: A list of int T = List[int] + +#: A list of Class +L = List[Class] diff --git a/tests/roots/test-ext-autodoc/target/typevar.py b/tests/roots/test-ext-autodoc/target/typevar.py index c330e2d88..ff2d46d19 100644 --- a/tests/roots/test-ext-autodoc/target/typevar.py +++ b/tests/roots/test-ext-autodoc/target/typevar.py @@ -1,3 +1,4 @@ +from datetime import date from typing import NewType, TypeVar #: T1 @@ -15,7 +16,7 @@ T4 = TypeVar("T4", covariant=True) T5 = TypeVar("T5", contravariant=True) #: T6 -T6 = NewType("T6", int) +T6 = NewType("T6", date) #: T7 T7 = TypeVar("T7", bound=int) @@ -26,4 +27,4 @@ class Class: T1 = TypeVar("T1") #: T6 - T6 = NewType("T6", int) + T6 = NewType("T6", date) diff --git a/tests/test_ext_autodoc.py b/tests/test_ext_autodoc.py index f70e505f3..62bbf83d0 100644 --- a/tests/test_ext_autodoc.py +++ b/tests/test_ext_autodoc.py @@ -1874,6 +1874,12 @@ def test_autodoc_GenericAlias(app): '', ' alias of :py:class:`~typing.List`\\ [:py:class:`int`]', '', + '.. py:attribute:: L', + ' :module: target.genericalias', + '', + ' A list of Class', + '', + '', '.. py:attribute:: T', ' :module: target.genericalias', '', @@ -1898,6 +1904,15 @@ def test_autodoc_GenericAlias(app): ' alias of :py:class:`~typing.List`\\ [:py:class:`int`]', '', '', + '.. py:data:: L', + ' :module: target.genericalias', + '', + ' A list of Class', + '', + ' alias of :py:class:`~typing.List`\\ ' + '[:py:class:`target.genericalias.Class`]', + '', + '', '.. py:data:: T', ' :module: target.genericalias', '', @@ -1935,7 +1950,7 @@ def test_autodoc_TypeVar(app): '', ' T6', '', - ' alias of :py:class:`int`', + ' alias of :py:class:`datetime.date`', '', '', '.. py:data:: T1', @@ -1975,7 +1990,7 @@ def test_autodoc_TypeVar(app): '', ' T6', '', - ' alias of :py:class:`int`', + ' alias of :py:class:`datetime.date`', '', '', '.. py:data:: T7', diff --git a/tests/test_ext_autodoc_autoattribute.py b/tests/test_ext_autodoc_autoattribute.py index 0dc84f195..9502b3c52 100644 --- a/tests/test_ext_autodoc_autoattribute.py +++ b/tests/test_ext_autodoc_autoattribute.py @@ -183,7 +183,7 @@ def test_autoattribute_NewType(app): '', ' T6', '', - ' alias of :py:class:`int`', + ' alias of :py:class:`datetime.date`', '', ] diff --git a/tests/test_ext_autodoc_autodata.py b/tests/test_ext_autodoc_autodata.py index 966d0359a..9fbfaaf39 100644 --- a/tests/test_ext_autodoc_autodata.py +++ b/tests/test_ext_autodoc_autodata.py @@ -111,7 +111,7 @@ def test_autodata_NewType(app): '', ' T6', '', - ' alias of :py:class:`int`', + ' alias of :py:class:`datetime.date`', '', ] diff --git a/tests/test_ext_autodoc_configs.py b/tests/test_ext_autodoc_configs.py index ab72d436d..e84e360e8 100644 --- a/tests/test_ext_autodoc_configs.py +++ b/tests/test_ext_autodoc_configs.py @@ -1231,6 +1231,62 @@ def test_autodoc_typehints_format_short(app): ] +@pytest.mark.sphinx('html', testroot='ext-autodoc', + confoverrides={'autodoc_typehints_format': "short"}) +def test_autodoc_typehints_format_short_for_class_alias(app): + actual = do_autodoc(app, 'class', 'target.classes.Alias') + assert list(actual) == [ + '', + '.. py:attribute:: Alias', + ' :module: target.classes', + '', + ' alias of :py:class:`~target.classes.Foo`', + ] + + +@pytest.mark.sphinx('html', testroot='ext-autodoc', + confoverrides={'autodoc_typehints_format': "short"}) +def test_autodoc_typehints_format_short_for_generic_alias(app): + actual = do_autodoc(app, 'data', 'target.genericalias.L') + if sys.version_info < (3, 7): + assert list(actual) == [ + '', + '.. py:data:: L', + ' :module: target.genericalias', + ' :value: typing.List[target.genericalias.Class]', + '', + ' A list of Class', + '', + ] + else: + assert list(actual) == [ + '', + '.. py:data:: L', + ' :module: target.genericalias', + '', + ' A list of Class', + '', + ' alias of :py:class:`~typing.List`\\ [:py:class:`~target.genericalias.Class`]', + '', + ] + + +@pytest.mark.sphinx('html', testroot='ext-autodoc', + confoverrides={'autodoc_typehints_format': "short"}) +def test_autodoc_typehints_format_short_for_newtype_alias(app): + actual = do_autodoc(app, 'data', 'target.typevar.T6') + assert list(actual) == [ + '', + '.. py:data:: T6', + ' :module: target.typevar', + '', + ' T6', + '', + ' alias of :py:class:`~datetime.date`', + '', + ] + + @pytest.mark.sphinx('html', testroot='ext-autodoc') def test_autodoc_default_options(app): # no settings diff --git a/tests/test_util_typing.py b/tests/test_util_typing.py index a49585588..c061fa085 100644 --- a/tests/test_util_typing.py +++ b/tests/test_util_typing.py @@ -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(): |