From 49f576569c6e4a63e762707a7fdf6a6ec31de985 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Mon, 20 Dec 2021 01:52:50 +0900 Subject: [PATCH 1/6] Fix autodoc: Failed to parse Literal type in py36 and py37 --- sphinx/domains/python.py | 8 +++++++- tests/test_domain_py.py | 9 +++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/sphinx/domains/python.py b/sphinx/domains/python.py index 5402ce37a8a..67b52c187e2 100644 --- a/sphinx/domains/python.py +++ b/sphinx/domains/python.py @@ -203,10 +203,16 @@ def unparse(node: ast.AST) -> List[Node]: return result else: if sys.version_info < (3, 8): - if isinstance(node, ast.Ellipsis): + if isinstance(node, ast.Bytes): + return [addnodes.desc_sig_literal_string('', repr(node.s))] + elif isinstance(node, ast.Ellipsis): return [addnodes.desc_sig_punctuation('', "...")] elif isinstance(node, ast.NameConstant): return [nodes.Text(node.value)] + elif isinstance(node, ast.Num): + return [addnodes.desc_sig_literal_string('', repr(node.n))] + elif isinstance(node, ast.Str): + return [addnodes.desc_sig_literal_string('', repr(node.s))] raise SyntaxError # unsupported syntax diff --git a/tests/test_domain_py.py b/tests/test_domain_py.py index f6af3e9af4a..eeb4f78a643 100644 --- a/tests/test_domain_py.py +++ b/tests/test_domain_py.py @@ -348,6 +348,15 @@ def test_parse_annotation(app): assert_node(doctree, ([pending_xref, "None"],)) assert_node(doctree[0], pending_xref, refdomain="py", reftype="obj", reftarget="None") + doctree = _parse_annotation("typing.Literal['a', 'b']", app.env) + assert_node(doctree, ([pending_xref, "typing.Literal"], + [desc_sig_punctuation, "["], + [desc_sig_literal_string, "'a'"], + [desc_sig_punctuation, ","], + desc_sig_space, + [desc_sig_literal_string, "'b'"], + [desc_sig_punctuation, "]"])) + def test_parse_annotation_suppress(app): doctree = _parse_annotation("~typing.Dict[str, str]", app.env) From 94cbce69daec2f12b131ea7463c0ab1bf8fdb03f Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Mon, 20 Dec 2021 01:53:20 +0900 Subject: [PATCH 2/6] Fix #9194: autodoc: types in typing module are not hyperlinked This converts types in typing module to valid references when `autodoc_unqualified_typehints` option enabled. --- CHANGES | 1 + sphinx/domains/python.py | 3 ++- tests/test_domain_py.py | 4 +++- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index 77a901e853c..c363a75b204 100644 --- a/CHANGES +++ b/CHANGES @@ -46,6 +46,7 @@ Bugs fixed with Python 3.10 * #9968: autodoc: instance variables are not shown if __init__ method has position-only-arguments +* #9194: autodoc: types under the "typing" module are not hyperlinked * #9947: i18n: topic directive having a bullet list can't be translatable * #9878: mathjax: MathJax configuration is placed after loading MathJax itself * #9857: Generated RFC links use outdated base url diff --git a/sphinx/domains/python.py b/sphinx/domains/python.py index 67b52c187e2..55545f4d274 100644 --- a/sphinx/domains/python.py +++ b/sphinx/domains/python.py @@ -83,7 +83,8 @@ class ModuleEntry(NamedTuple): def type_to_xref(target: str, env: BuildEnvironment = None, suppress_prefix: bool = False ) -> addnodes.pending_xref: """Convert a type string to a cross reference node.""" - if target == 'None': + if target == 'None' or target.startswith('typing.'): + # typing module provides non-class types. Obj reference is good to refer them. reftype = 'obj' else: reftype = 'class' diff --git a/tests/test_domain_py.py b/tests/test_domain_py.py index eeb4f78a643..d986b62f334 100644 --- a/tests/test_domain_py.py +++ b/tests/test_domain_py.py @@ -348,6 +348,7 @@ def test_parse_annotation(app): assert_node(doctree, ([pending_xref, "None"],)) assert_node(doctree[0], pending_xref, refdomain="py", reftype="obj", reftarget="None") + # Literal type makes an object-reference (not a class reference) doctree = _parse_annotation("typing.Literal['a', 'b']", app.env) assert_node(doctree, ([pending_xref, "typing.Literal"], [desc_sig_punctuation, "["], @@ -356,6 +357,7 @@ def test_parse_annotation(app): desc_sig_space, [desc_sig_literal_string, "'b'"], [desc_sig_punctuation, "]"])) + assert_node(doctree[0], pending_xref, refdomain="py", reftype="obj", reftarget="typing.Literal") def test_parse_annotation_suppress(app): @@ -367,7 +369,7 @@ def test_parse_annotation_suppress(app): desc_sig_space, [pending_xref, "str"], [desc_sig_punctuation, "]"])) - assert_node(doctree[0], pending_xref, refdomain="py", reftype="class", reftarget="typing.Dict") + assert_node(doctree[0], pending_xref, refdomain="py", reftype="obj", reftarget="typing.Dict") @pytest.mark.skipif(sys.version_info < (3, 8), reason='python 3.8+ is required.') From 0a5783f75b7ecf1aaa27a9e1b2b2c8a573a2bfb5 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Fri, 24 Dec 2021 10:21:11 +0900 Subject: [PATCH 3/6] py domain: Suppress the leading "typing." module from typehints To support creating hyperlinks to container types naturally, py domain should take fully-qualified typehints for them. But nobody wants to show "typing." module name on the signature. So this suppresses them automatically. --- sphinx/domains/python.py | 4 +++- tests/test_domain_py.py | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/sphinx/domains/python.py b/sphinx/domains/python.py index 55545f4d274..240db309b4e 100644 --- a/sphinx/domains/python.py +++ b/sphinx/domains/python.py @@ -105,6 +105,8 @@ def type_to_xref(target: str, env: BuildEnvironment = None, suppress_prefix: boo text = target.split('.')[-1] elif suppress_prefix: text = target.split('.')[-1] + elif target.startswith('typing.'): + text = target[7:] else: text = target @@ -1488,7 +1490,7 @@ def istyping(s: str) -> bool: return None elif node.get('reftype') in ('class', 'obj') and node.get('reftarget') == 'None': return contnode - elif node.get('reftype') in ('class', 'exc'): + elif node.get('reftype') in ('class', 'obj', 'exc'): reftarget = node.get('reftarget') if inspect.isclass(getattr(builtins, reftarget, None)): # built-in class diff --git a/tests/test_domain_py.py b/tests/test_domain_py.py index d986b62f334..ca0148cbb86 100644 --- a/tests/test_domain_py.py +++ b/tests/test_domain_py.py @@ -350,7 +350,7 @@ def test_parse_annotation(app): # Literal type makes an object-reference (not a class reference) doctree = _parse_annotation("typing.Literal['a', 'b']", app.env) - assert_node(doctree, ([pending_xref, "typing.Literal"], + assert_node(doctree, ([pending_xref, "Literal"], [desc_sig_punctuation, "["], [desc_sig_literal_string, "'a'"], [desc_sig_punctuation, ","], @@ -384,7 +384,7 @@ def test_parse_annotation_Literal(app): [desc_sig_punctuation, "]"])) doctree = _parse_annotation("typing.Literal[0, 1, 'abc']", app.env) - assert_node(doctree, ([pending_xref, "typing.Literal"], + assert_node(doctree, ([pending_xref, "Literal"], [desc_sig_punctuation, "["], [desc_sig_literal_number, "0"], [desc_sig_punctuation, ","], From bdbad40f579bd7db7f837bfa998320658add3b89 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Fri, 24 Dec 2021 10:50:04 +0900 Subject: [PATCH 4/6] Fix #9194: autodoc: Prepend the "typing" module name on the signature To create hyperlinks to container types automatically, this prepends the module names for the types under "typing" module. --- sphinx/util/inspect.py | 4 +- sphinx/util/typing.py | 52 ++++++++++++--------- tests/test_ext_autodoc_autofunction.py | 2 +- tests/test_ext_autodoc_automodule.py | 2 +- tests/test_ext_autodoc_configs.py | 5 +- tests/test_ext_autodoc_preserve_defaults.py | 8 ++-- tests/test_util_inspect.py | 42 +++++++++-------- tests/test_util_typing.py | 26 +++++++++++ 8 files changed, 90 insertions(+), 51 deletions(-) diff --git a/sphinx/util/inspect.py b/sphinx/util/inspect.py index c67369e8935..d5c555afb8a 100644 --- a/sphinx/util/inspect.py +++ b/sphinx/util/inspect.py @@ -774,7 +774,7 @@ def stringify_signature(sig: inspect.Signature, show_annotation: bool = True, if show_annotation and param.annotation is not param.empty: arg.write(': ') - arg.write(stringify_annotation(param.annotation, unqualified_typehints)) + arg.write(stringify_annotation(param.annotation, unqualified_typehints, True)) if param.default is not param.empty: if show_annotation and param.annotation is not param.empty: arg.write(' = ') @@ -794,7 +794,7 @@ def stringify_signature(sig: inspect.Signature, show_annotation: bool = True, show_return_annotation is False): return '(%s)' % ', '.join(args) else: - annotation = stringify_annotation(sig.return_annotation, unqualified_typehints) + annotation = stringify_annotation(sig.return_annotation, unqualified_typehints, True) return '(%s) -> %s' % (', '.join(args), annotation) diff --git a/sphinx/util/typing.py b/sphinx/util/typing.py index 259384ec70f..d00c3ba09b7 100644 --- a/sphinx/util/typing.py +++ b/sphinx/util/typing.py @@ -299,11 +299,12 @@ def _restify_py36(cls: Optional[Type]) -> str: return ':py:obj:`%s.%s`' % (cls.__module__, qualname) -def stringify(annotation: Any, smartref: bool = False) -> str: +def stringify(annotation: Any, smartref: bool = False, show_typing: bool = False) -> str: """Stringify type annotation object. :param smartref: If true, add "~" prefix to the result to remove the leading module and class names from the reference text + :param show_typing: If true, do not suppress the "typing" module name """ from sphinx.util import inspect # lazy loading @@ -319,7 +320,7 @@ def stringify(annotation: Any, smartref: bool = False) -> str: else: return annotation elif isinstance(annotation, TypeVar): - if annotation.__module__ == 'typing': + if show_typing is False and annotation.__module__ == 'typing': return annotation.__name__ else: return prefix + '.'.join([annotation.__module__, annotation.__name__]) @@ -347,12 +348,12 @@ def stringify(annotation: Any, smartref: bool = False) -> str: return '...' if sys.version_info >= (3, 7): # py37+ - return _stringify_py37(annotation, smartref) + return _stringify_py37(annotation, smartref, show_typing) else: - return _stringify_py36(annotation, smartref) + return _stringify_py36(annotation, smartref, show_typing) -def _stringify_py37(annotation: Any, smartref: bool = False) -> str: +def _stringify_py37(annotation: Any, smartref: bool = False, show_typing: bool = False) -> str: """stringify() for py37+.""" module = getattr(annotation, '__module__', None) modprefix = '' @@ -364,10 +365,12 @@ def _stringify_py37(annotation: Any, smartref: bool = False) -> str: elif getattr(annotation, '__qualname__', None): qualname = annotation.__qualname__ else: - qualname = stringify(annotation.__origin__) # ex. Union + qualname = stringify(annotation.__origin__).replace('typing.', '') # ex. Union if smartref: modprefix = '~%s.' % module + elif show_typing: + modprefix = '%s.' % module elif hasattr(annotation, '__qualname__'): if smartref: modprefix = '~%s.' % module @@ -376,7 +379,7 @@ def _stringify_py37(annotation: Any, smartref: bool = False) -> str: qualname = annotation.__qualname__ elif hasattr(annotation, '__origin__'): # instantiated generic provided by a user - qualname = stringify(annotation.__origin__, smartref) + qualname = stringify(annotation.__origin__, smartref, show_typing) elif UnionType and isinstance(annotation, UnionType): # types.Union (for py3.10+) qualname = 'types.Union' else: @@ -391,13 +394,15 @@ def _stringify_py37(annotation: Any, smartref: bool = False) -> str: 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, smartref) for a in annotation.__args__[:-1]) + args = ', '.join(stringify(a, smartref, show_typing) for a + in annotation.__args__[:-1]) return '%sOptional[%sUnion[%s]]' % (modprefix, modprefix, args) else: - return '%sOptional[%s]' % (modprefix, - stringify(annotation.__args__[0], smartref)) + return '%sOptional[%s]' % (modprefix, stringify(annotation.__args__[0], + smartref, show_typing)) else: - args = ', '.join(stringify(a, smartref) for a in annotation.__args__) + args = ', '.join(stringify(a, smartref, show_typing) for a + in annotation.__args__) return '%sUnion[%s]' % (modprefix, args) elif qualname == 'types.Union': if len(annotation.__args__) > 1 and None in annotation.__args__: @@ -406,25 +411,27 @@ def _stringify_py37(annotation: Any, smartref: bool = False) -> str: else: return ' | '.join(stringify(a) for a in annotation.__args__) elif qualname == 'Callable': - args = ', '.join(stringify(a, smartref) for a in annotation.__args__[:-1]) - returns = stringify(annotation.__args__[-1], smartref) + args = ', '.join(stringify(a, smartref, show_typing) for a + in annotation.__args__[:-1]) + returns = stringify(annotation.__args__[-1], smartref, show_typing) return '%s%s[[%s], %s]' % (modprefix, qualname, args, returns) elif qualname == 'Literal': args = ', '.join(repr(a) for a in annotation.__args__) return '%s%s[%s]' % (modprefix, qualname, args) elif str(annotation).startswith('typing.Annotated'): # for py39+ - return stringify(annotation.__args__[0], smartref) + return stringify(annotation.__args__[0], smartref, show_typing) 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 else: - args = ', '.join(stringify(a, smartref) for a in annotation.__args__) + args = ', '.join(stringify(a, smartref, show_typing) for a + in annotation.__args__) return '%s%s[%s]' % (modprefix, qualname, args) return modprefix + qualname -def _stringify_py36(annotation: Any, smartref: bool = False) -> str: +def _stringify_py36(annotation: Any, smartref: bool = False, show_typing: bool = False) -> str: """stringify() for py36.""" module = getattr(annotation, '__module__', None) modprefix = '' @@ -442,6 +449,8 @@ def _stringify_py36(annotation: Any, smartref: bool = False) -> str: if smartref: modprefix = '~%s.' % module + elif show_typing: + modprefix = '%s.' % module elif hasattr(annotation, '__qualname__'): if smartref: modprefix = '~%s.' % module @@ -455,7 +464,7 @@ def _stringify_py36(annotation: Any, smartref: bool = False) -> str: not hasattr(annotation, '__tuple_params__')): # for Python 3.6 params = annotation.__args__ if params: - param_str = ', '.join(stringify(p, smartref) for p in params) + param_str = ', '.join(stringify(p, smartref, show_typing) for p in params) return '%s%s[%s]' % (modprefix, qualname, param_str) else: return modprefix + qualname @@ -466,12 +475,12 @@ def _stringify_py36(annotation: Any, smartref: bool = False) -> str: elif annotation.__origin__ == Generator: # type: ignore params = annotation.__args__ # type: ignore else: # typing.Callable - args = ', '.join(stringify(arg, smartref) for arg + args = ', '.join(stringify(arg, smartref, show_typing) for arg in annotation.__args__[:-1]) # type: ignore result = stringify(annotation.__args__[-1]) # type: ignore return '%s%s[[%s], %s]' % (modprefix, qualname, args, result) if params is not None: - param_str = ', '.join(stringify(p, smartref) for p in params) + param_str = ', '.join(stringify(p, smartref, show_typing) for p in params) return '%s%s[%s]' % (modprefix, qualname, param_str) elif (hasattr(annotation, '__origin__') and annotation.__origin__ is typing.Union): @@ -479,12 +488,13 @@ def _stringify_py36(annotation: Any, smartref: bool = False) -> str: if params is not None: if len(params) > 1 and params[-1] is NoneType: if len(params) > 2: - param_str = ", ".join(stringify(p, smartref) for p in params[:-1]) + param_str = ", ".join(stringify(p, smartref, show_typing) for p + in params[:-1]) return '%sOptional[%sUnion[%s]]' % (modprefix, modprefix, param_str) else: return '%sOptional[%s]' % (modprefix, stringify(params[0])) else: - param_str = ', '.join(stringify(p, smartref) for p in params) + param_str = ', '.join(stringify(p, smartref, show_typing) for p in params) return '%sUnion[%s]' % (modprefix, param_str) return modprefix + qualname diff --git a/tests/test_ext_autodoc_autofunction.py b/tests/test_ext_autodoc_autofunction.py index 52af51abbfc..e3db8ca0d7e 100644 --- a/tests/test_ext_autodoc_autofunction.py +++ b/tests/test_ext_autodoc_autofunction.py @@ -162,7 +162,7 @@ def test_wrapped_function_contextmanager(app): actual = do_autodoc(app, 'function', 'target.wrappedfunction.feeling_good') assert list(actual) == [ '', - '.. py:function:: feeling_good(x: int, y: int) -> Generator', + '.. py:function:: feeling_good(x: int, y: int) -> typing.Generator', ' :module: target.wrappedfunction', '', " You'll feel better in this context!", diff --git a/tests/test_ext_autodoc_automodule.py b/tests/test_ext_autodoc_automodule.py index 59296a981a3..1f4d590793f 100644 --- a/tests/test_ext_autodoc_automodule.py +++ b/tests/test_ext_autodoc_automodule.py @@ -130,4 +130,4 @@ def test_subclass_of_mocked_object(app): options = {'members': None} actual = do_autodoc(app, 'module', 'target.need_mocks', options) - assert '.. py:class:: Inherited(*args: Any, **kwargs: Any)' in actual + assert '.. py:class:: Inherited(*args: typing.Any, **kwargs: typing.Any)' in actual diff --git a/tests/test_ext_autodoc_configs.py b/tests/test_ext_autodoc_configs.py index f3bcd6a97a1..899bf9ff024 100644 --- a/tests/test_ext_autodoc_configs.py +++ b/tests/test_ext_autodoc_configs.py @@ -612,7 +612,7 @@ def test_autodoc_typehints_signature(app): ' :type: int', '', '', - '.. py:class:: Math(s: str, o: Optional[Any] = None)', + '.. py:class:: Math(s: str, o: typing.Optional[typing.Any] = None)', ' :module: target.typehints', '', '', @@ -677,7 +677,8 @@ def test_autodoc_typehints_signature(app): ' :module: target.typehints', '', '', - '.. py:function:: tuple_args(x: Tuple[int, Union[int, str]]) -> Tuple[int, int]', + '.. py:function:: tuple_args(x: typing.Tuple[int, typing.Union[int, str]]) ' + '-> typing.Tuple[int, int]', ' :module: target.typehints', '', ] diff --git a/tests/test_ext_autodoc_preserve_defaults.py b/tests/test_ext_autodoc_preserve_defaults.py index 955c60aa4bf..b9634eef871 100644 --- a/tests/test_ext_autodoc_preserve_defaults.py +++ b/tests/test_ext_autodoc_preserve_defaults.py @@ -36,15 +36,15 @@ def test_preserve_defaults(app): ' docstring', '', '', - ' .. py:method:: Class.meth(name: str = CONSTANT, sentinel: Any = SENTINEL, ' - 'now: datetime.datetime = datetime.now(), color: int = %s) -> None' % color, + ' .. py:method:: Class.meth(name: str = CONSTANT, sentinel: typing.Any = ' + 'SENTINEL, now: datetime.datetime = datetime.now(), color: int = %s) -> None' % color, ' :module: target.preserve_defaults', '', ' docstring', '', '', - '.. py:function:: foo(name: str = CONSTANT, sentinel: Any = SENTINEL, now: ' - 'datetime.datetime = datetime.now(), color: int = %s) -> None' % color, + '.. py:function:: foo(name: str = CONSTANT, sentinel: typing.Any = SENTINEL, ' + 'now: datetime.datetime = datetime.now(), color: int = %s) -> None' % color, ' :module: target.preserve_defaults', '', ' docstring', diff --git a/tests/test_util_inspect.py b/tests/test_util_inspect.py index f331acb233e..25709a37f61 100644 --- a/tests/test_util_inspect.py +++ b/tests/test_util_inspect.py @@ -157,21 +157,22 @@ def test_signature_annotations(): # Generic types with concrete parameters sig = inspect.signature(f1) - assert stringify_signature(sig) == '(x: List[int]) -> List[int]' + assert stringify_signature(sig) == '(x: typing.List[int]) -> typing.List[int]' # TypeVars and generic types with TypeVars sig = inspect.signature(f2) if sys.version_info < (3, 7): - assert stringify_signature(sig) == '(x: List[T], y: List[T_co], z: T) -> List[T_contra]' + assert stringify_signature(sig) == ('(x: typing.List[T], y: typing.List[T_co], z: T) ' + '-> typing.List[T_contra]') else: - assert stringify_signature(sig) == ('(x: List[tests.typing_test_data.T],' - ' y: List[tests.typing_test_data.T_co],' + assert stringify_signature(sig) == ('(x: typing.List[tests.typing_test_data.T],' + ' y: typing.List[tests.typing_test_data.T_co],' ' z: tests.typing_test_data.T' - ') -> List[tests.typing_test_data.T_contra]') + ') -> typing.List[tests.typing_test_data.T_contra]') # Union types sig = inspect.signature(f3) - assert stringify_signature(sig) == '(x: Union[str, numbers.Integral]) -> None' + assert stringify_signature(sig) == '(x: typing.Union[str, numbers.Integral]) -> None' # Quoted annotations sig = inspect.signature(f4) @@ -187,18 +188,18 @@ def test_signature_annotations(): # Space around '=' for defaults sig = inspect.signature(f7) - assert stringify_signature(sig) == '(x: Optional[int] = None, y: dict = {}) -> None' + assert stringify_signature(sig) == '(x: typing.Optional[int] = None, y: dict = {}) -> None' # Callable types sig = inspect.signature(f8) - assert stringify_signature(sig) == '(x: Callable[[int, str], int]) -> None' + assert stringify_signature(sig) == '(x: typing.Callable[[int, str], int]) -> None' sig = inspect.signature(f9) - assert stringify_signature(sig) == '(x: Callable) -> None' + assert stringify_signature(sig) == '(x: typing.Callable) -> None' # Tuple types sig = inspect.signature(f10) - assert stringify_signature(sig) == '(x: Tuple[int, str], y: Tuple[int, ...]) -> None' + assert stringify_signature(sig) == '(x: typing.Tuple[int, str], y: typing.Tuple[int, ...]) -> None' # Instance annotations sig = inspect.signature(f11) @@ -206,24 +207,24 @@ def test_signature_annotations(): # tuple with more than two items sig = inspect.signature(f12) - assert stringify_signature(sig) == '() -> Tuple[int, str, int]' + assert stringify_signature(sig) == '() -> typing.Tuple[int, str, int]' # optional sig = inspect.signature(f13) - assert stringify_signature(sig) == '() -> Optional[str]' + assert stringify_signature(sig) == '() -> typing.Optional[str]' # optional union sig = inspect.signature(f20) - assert stringify_signature(sig) in ('() -> Optional[Union[int, str]]', - '() -> Optional[Union[str, int]]') + assert stringify_signature(sig) in ('() -> typing.Optional[typing.Union[int, str]]', + '() -> typing.Optional[typing.Union[str, int]]') # Any sig = inspect.signature(f14) - assert stringify_signature(sig) == '() -> Any' + assert stringify_signature(sig) == '() -> typing.Any' # ForwardRef sig = inspect.signature(f15) - assert stringify_signature(sig) == '(x: Unknown, y: int) -> Any' + assert stringify_signature(sig) == '(x: Unknown, y: int) -> typing.Any' # keyword only arguments (1) sig = inspect.signature(f16) @@ -234,7 +235,8 @@ def test_signature_annotations(): assert stringify_signature(sig) == '(*, arg3, arg4)' sig = inspect.signature(f18) - assert stringify_signature(sig) == '(self, arg1: Union[int, Tuple] = 10) -> List[Dict]' + assert stringify_signature(sig) == ('(self, arg1: typing.Union[int, typing.Tuple] = 10) -> ' + 'typing.List[typing.Dict]') # annotations for variadic and keyword parameters sig = inspect.signature(f19) @@ -246,10 +248,10 @@ def test_signature_annotations(): # type hints by string sig = inspect.signature(Node.children) - assert stringify_signature(sig) == '(self) -> List[tests.typing_test_data.Node]' + assert stringify_signature(sig) == '(self) -> typing.List[tests.typing_test_data.Node]' sig = inspect.signature(Node.__init__) - assert stringify_signature(sig) == '(self, parent: Optional[tests.typing_test_data.Node]) -> None' + assert stringify_signature(sig) == '(self, parent: typing.Optional[tests.typing_test_data.Node]) -> None' # show_annotation is False sig = inspect.signature(f7) @@ -257,7 +259,7 @@ def test_signature_annotations(): # show_return_annotation is False sig = inspect.signature(f7) - assert stringify_signature(sig, show_return_annotation=False) == '(x: Optional[int] = None, y: dict = {})' + assert stringify_signature(sig, show_return_annotation=False) == '(x: typing.Optional[int] = None, y: dict = {})' # unqualified_typehints is True sig = inspect.signature(f7) diff --git a/tests/test_util_typing.py b/tests/test_util_typing.py index 0b2324e294b..f17221458e7 100644 --- a/tests/test_util_typing.py +++ b/tests/test_util_typing.py @@ -197,41 +197,53 @@ def test_stringify(): assert stringify(TracebackType, True) == "~types.TracebackType" assert stringify(Any, False) == "Any" + assert stringify(Any, False, True) == "typing.Any" assert stringify(Any, True) == "~typing.Any" def test_stringify_type_hints_containers(): assert stringify(List, False) == "List" + assert stringify(List, False, True) == "typing.List" assert stringify(List, True) == "~typing.List" assert stringify(Dict, False) == "Dict" + assert stringify(Dict, False, True) == "typing.Dict" assert stringify(Dict, True) == "~typing.Dict" assert stringify(List[int], False) == "List[int]" + assert stringify(List[int], False, True) == "typing.List[int]" assert stringify(List[int], True) == "~typing.List[int]" assert stringify(List[str], False) == "List[str]" + assert stringify(List[str], False, True) == "typing.List[str]" assert stringify(List[str], True) == "~typing.List[str]" assert stringify(Dict[str, float], False) == "Dict[str, float]" + assert stringify(Dict[str, float], False, True) == "typing.Dict[str, float]" assert stringify(Dict[str, float], True) == "~typing.Dict[str, float]" assert stringify(Tuple[str, str, str], False) == "Tuple[str, str, str]" + assert stringify(Tuple[str, str, str], False, True) == "typing.Tuple[str, str, str]" assert stringify(Tuple[str, str, str], True) == "~typing.Tuple[str, str, str]" assert stringify(Tuple[str, ...], False) == "Tuple[str, ...]" + assert stringify(Tuple[str, ...], False, True) == "typing.Tuple[str, ...]" assert stringify(Tuple[str, ...], True) == "~typing.Tuple[str, ...]" assert stringify(Tuple[()], False) == "Tuple[()]" + assert stringify(Tuple[()], False, True) == "typing.Tuple[()]" assert stringify(Tuple[()], True) == "~typing.Tuple[()]" assert stringify(List[Dict[str, Tuple]], False) == "List[Dict[str, Tuple]]" + assert stringify(List[Dict[str, Tuple]], False, True) == "typing.List[typing.Dict[str, typing.Tuple]]" assert stringify(List[Dict[str, Tuple]], True) == "~typing.List[~typing.Dict[str, ~typing.Tuple]]" assert stringify(MyList[Tuple[int, int]], False) == "tests.test_util_typing.MyList[Tuple[int, int]]" + assert stringify(MyList[Tuple[int, int]], False, True) == "tests.test_util_typing.MyList[typing.Tuple[int, int]]" assert stringify(MyList[Tuple[int, int]], True) == "~tests.test_util_typing.MyList[~typing.Tuple[int, int]]" assert stringify(Generator[None, None, None], False) == "Generator[None, None, None]" + assert stringify(Generator[None, None, None], False, True) == "typing.Generator[None, None, None]" assert stringify(Generator[None, None, None], True) == "~typing.Generator[None, None, None]" @@ -288,45 +300,58 @@ def test_stringify_type_hints_string(): def test_stringify_type_hints_Callable(): assert stringify(Callable, False) == "Callable" + assert stringify(Callable, False, True) == "typing.Callable" assert stringify(Callable, True) == "~typing.Callable" if sys.version_info >= (3, 7): assert stringify(Callable[[str], int], False) == "Callable[[str], int]" + assert stringify(Callable[[str], int], False, True) == "typing.Callable[[str], int]" assert stringify(Callable[[str], int], True) == "~typing.Callable[[str], int]" assert stringify(Callable[..., int], False) == "Callable[[...], int]" + assert stringify(Callable[..., int], False, True) == "typing.Callable[[...], int]" assert stringify(Callable[..., int], True) == "~typing.Callable[[...], int]" else: assert stringify(Callable[[str], int], False) == "Callable[str, int]" + assert stringify(Callable[[str], int], False, True) == "typing.Callable[str, int]" assert stringify(Callable[[str], int], True) == "~typing.Callable[str, int]" assert stringify(Callable[..., int], False) == "Callable[..., int]" + assert stringify(Callable[..., int], False, True) == "typing.Callable[..., int]" assert stringify(Callable[..., int], True) == "~typing.Callable[..., int]" def test_stringify_type_hints_Union(): assert stringify(Optional[int], False) == "Optional[int]" + assert stringify(Optional[int], False, True) == "typing.Optional[int]" assert stringify(Optional[int], True) == "~typing.Optional[int]" assert stringify(Union[str, None], False) == "Optional[str]" + assert stringify(Union[str, None], False, True) == "typing.Optional[str]" assert stringify(Union[str, None], True) == "~typing.Optional[str]" assert stringify(Union[int, str], False) == "Union[int, str]" + assert stringify(Union[int, str], False, True) == "typing.Union[int, str]" assert stringify(Union[int, str], True) == "~typing.Union[int, str]" if sys.version_info >= (3, 7): assert stringify(Union[int, Integral], False) == "Union[int, numbers.Integral]" + assert stringify(Union[int, Integral], False, True) == "typing.Union[int, numbers.Integral]" assert stringify(Union[int, Integral], True) == "~typing.Union[int, ~numbers.Integral]" assert (stringify(Union[MyClass1, MyClass2], False) == "Union[tests.test_util_typing.MyClass1, tests.test_util_typing.]") + assert (stringify(Union[MyClass1, MyClass2], False, True) == + "typing.Union[tests.test_util_typing.MyClass1, tests.test_util_typing.]") assert (stringify(Union[MyClass1, MyClass2], True) == "~typing.Union[~tests.test_util_typing.MyClass1, ~tests.test_util_typing.]") else: assert stringify(Union[int, Integral], False) == "numbers.Integral" + assert stringify(Union[int, Integral], False, True) == "numbers.Integral" assert stringify(Union[int, Integral], True) == "~numbers.Integral" assert stringify(Union[MyClass1, MyClass2], False) == "tests.test_util_typing.MyClass1" + assert stringify(Union[MyClass1, MyClass2], False, True) == "tests.test_util_typing.MyClass1" assert stringify(Union[MyClass1, MyClass2], True) == "~tests.test_util_typing.MyClass1" @@ -391,6 +416,7 @@ def test_stringify_type_hints_alias(): def test_stringify_type_Literal(): from typing import Literal # type: ignore assert stringify(Literal[1, "2", "\r"], False) == "Literal[1, '2', '\\r']" + assert stringify(Literal[1, "2", "\r"], False, True) == "typing.Literal[1, '2', '\\r']" assert stringify(Literal[1, "2", "\r"], True) == "~typing.Literal[1, '2', '\\r']" From 2ea9118181df74ea6a8371d72b2a4ac7da658438 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sun, 26 Dec 2021 02:03:59 +0900 Subject: [PATCH 5/6] refactor: Merge arguments of sphinx.util.typing:stringify() --- sphinx/util/inspect.py | 9 +- sphinx/util/typing.py | 84 +++++----- tests/test_util_inspect.py | 6 +- tests/test_util_typing.py | 306 ++++++++++++++++++------------------- 4 files changed, 207 insertions(+), 198 deletions(-) diff --git a/sphinx/util/inspect.py b/sphinx/util/inspect.py index d5c555afb8a..8543c9ee8da 100644 --- a/sphinx/util/inspect.py +++ b/sphinx/util/inspect.py @@ -752,6 +752,11 @@ def stringify_signature(sig: inspect.Signature, show_annotation: bool = True, :param unqualified_typehints: Show annotations as unqualified (ex. io.StringIO -> StringIO) """ + if unqualified_typehints: + mode = 'smart' + else: + mode = 'fully-qualified' + args = [] last_kind = None for param in sig.parameters.values(): @@ -774,7 +779,7 @@ def stringify_signature(sig: inspect.Signature, show_annotation: bool = True, if show_annotation and param.annotation is not param.empty: arg.write(': ') - arg.write(stringify_annotation(param.annotation, unqualified_typehints, True)) + arg.write(stringify_annotation(param.annotation, mode)) if param.default is not param.empty: if show_annotation and param.annotation is not param.empty: arg.write(' = ') @@ -794,7 +799,7 @@ def stringify_signature(sig: inspect.Signature, show_annotation: bool = True, show_return_annotation is False): return '(%s)' % ', '.join(args) else: - annotation = stringify_annotation(sig.return_annotation, unqualified_typehints, True) + annotation = stringify_annotation(sig.return_annotation, mode) return '(%s) -> %s' % (', '.join(args), annotation) diff --git a/sphinx/util/typing.py b/sphinx/util/typing.py index d00c3ba09b7..005599f79f9 100644 --- a/sphinx/util/typing.py +++ b/sphinx/util/typing.py @@ -299,19 +299,25 @@ def _restify_py36(cls: Optional[Type]) -> str: return ':py:obj:`%s.%s`' % (cls.__module__, qualname) -def stringify(annotation: Any, smartref: bool = False, show_typing: bool = False) -> str: +def stringify(annotation: Any, mode: str = 'fully-qualified-except-typing') -> str: """Stringify type annotation object. - :param smartref: If true, add "~" prefix to the result to remove the leading - module and class names from the reference text - :param show_typing: If true, do not suppress the "typing" module name + :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. + 'fully-qualified' + Show the module name and qualified name of the annotation. """ from sphinx.util import inspect # lazy loading - if smartref: - prefix = '~' + if mode == 'smart': + modprefix = '~' else: - prefix = '' + modprefix = '' if isinstance(annotation, str): if annotation.startswith("'") and annotation.endswith("'"): @@ -320,14 +326,15 @@ def stringify(annotation: Any, smartref: bool = False, show_typing: bool = False else: return annotation elif isinstance(annotation, TypeVar): - if show_typing is False and annotation.__module__ == 'typing': + if (annotation.__module__ == 'typing' and + mode in ('fully-qualified-except-typing', 'smart')): return annotation.__name__ else: - return prefix + '.'.join([annotation.__module__, annotation.__name__]) + return modprefix + '.'.join([annotation.__module__, annotation.__name__]) elif inspect.isNewType(annotation): if sys.version_info > (3, 10): # newtypes have correct module info since Python 3.10+ - return prefix + '%s.%s' % (annotation.__module__, annotation.__name__) + return modprefix + '%s.%s' % (annotation.__module__, annotation.__name__) else: return annotation.__name__ elif not annotation: @@ -335,7 +342,7 @@ def stringify(annotation: Any, smartref: bool = False, show_typing: bool = False elif annotation is NoneType: return 'None' elif annotation in INVALID_BUILTIN_CLASSES: - return prefix + INVALID_BUILTIN_CLASSES[annotation] + return modprefix + INVALID_BUILTIN_CLASSES[annotation] elif str(annotation).startswith('typing.Annotated'): # for py310+ pass elif (getattr(annotation, '__module__', None) == 'builtins' and @@ -348,12 +355,12 @@ def stringify(annotation: Any, smartref: bool = False, show_typing: bool = False return '...' if sys.version_info >= (3, 7): # py37+ - return _stringify_py37(annotation, smartref, show_typing) + return _stringify_py37(annotation, mode) else: - return _stringify_py36(annotation, smartref, show_typing) + return _stringify_py36(annotation, mode) -def _stringify_py37(annotation: Any, smartref: bool = False, show_typing: bool = False) -> str: +def _stringify_py37(annotation: Any, mode: str = 'fully-qualified-except-typing') -> str: """stringify() for py37+.""" module = getattr(annotation, '__module__', None) modprefix = '' @@ -367,19 +374,19 @@ def _stringify_py37(annotation: Any, smartref: bool = False, show_typing: bool = else: qualname = stringify(annotation.__origin__).replace('typing.', '') # ex. Union - if smartref: + if mode == 'smart': modprefix = '~%s.' % module - elif show_typing: + elif mode == 'fully-qualified': modprefix = '%s.' % module elif hasattr(annotation, '__qualname__'): - if smartref: + if mode == 'smart': modprefix = '~%s.' % module else: modprefix = '%s.' % module qualname = annotation.__qualname__ elif hasattr(annotation, '__origin__'): # instantiated generic provided by a user - qualname = stringify(annotation.__origin__, smartref, show_typing) + qualname = stringify(annotation.__origin__, mode) elif UnionType and isinstance(annotation, UnionType): # types.Union (for py3.10+) qualname = 'types.Union' else: @@ -394,15 +401,13 @@ def _stringify_py37(annotation: Any, smartref: bool = False, show_typing: bool = 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, smartref, show_typing) for a - in annotation.__args__[:-1]) + args = ', '.join(stringify(a, mode) for a in annotation.__args__[:-1]) return '%sOptional[%sUnion[%s]]' % (modprefix, modprefix, args) else: - return '%sOptional[%s]' % (modprefix, stringify(annotation.__args__[0], - smartref, show_typing)) + return '%sOptional[%s]' % (modprefix, + stringify(annotation.__args__[0], mode)) else: - args = ', '.join(stringify(a, smartref, show_typing) for a - in annotation.__args__) + args = ', '.join(stringify(a, mode) for a in annotation.__args__) return '%sUnion[%s]' % (modprefix, args) elif qualname == 'types.Union': if len(annotation.__args__) > 1 and None in annotation.__args__: @@ -411,27 +416,25 @@ def _stringify_py37(annotation: Any, smartref: bool = False, show_typing: bool = else: return ' | '.join(stringify(a) for a in annotation.__args__) elif qualname == 'Callable': - args = ', '.join(stringify(a, smartref, show_typing) for a - in annotation.__args__[:-1]) - returns = stringify(annotation.__args__[-1], smartref, show_typing) + args = ', '.join(stringify(a, mode) for a in annotation.__args__[:-1]) + returns = stringify(annotation.__args__[-1], mode) return '%s%s[[%s], %s]' % (modprefix, qualname, args, returns) elif qualname == 'Literal': args = ', '.join(repr(a) for a in annotation.__args__) return '%s%s[%s]' % (modprefix, qualname, args) elif str(annotation).startswith('typing.Annotated'): # for py39+ - return stringify(annotation.__args__[0], smartref, show_typing) + return stringify(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 else: - args = ', '.join(stringify(a, smartref, show_typing) for a - in annotation.__args__) + args = ', '.join(stringify(a, mode) for a in annotation.__args__) return '%s%s[%s]' % (modprefix, qualname, args) return modprefix + qualname -def _stringify_py36(annotation: Any, smartref: bool = False, show_typing: bool = False) -> str: +def _stringify_py36(annotation: Any, mode: str = 'fully-qualified-except-typing') -> str: """stringify() for py36.""" module = getattr(annotation, '__module__', None) modprefix = '' @@ -447,12 +450,12 @@ def _stringify_py36(annotation: Any, smartref: bool = False, show_typing: bool = else: qualname = repr(annotation).replace('typing.', '') - if smartref: + if mode == 'smart': modprefix = '~%s.' % module - elif show_typing: + elif mode == 'fully-qualified': modprefix = '%s.' % module elif hasattr(annotation, '__qualname__'): - if smartref: + if mode == 'smart': modprefix = '~%s.' % module else: modprefix = '%s.' % module @@ -464,7 +467,7 @@ def _stringify_py36(annotation: Any, smartref: bool = False, show_typing: bool = not hasattr(annotation, '__tuple_params__')): # for Python 3.6 params = annotation.__args__ if params: - param_str = ', '.join(stringify(p, smartref, show_typing) for p in params) + param_str = ', '.join(stringify(p, mode) for p in params) return '%s%s[%s]' % (modprefix, qualname, param_str) else: return modprefix + qualname @@ -475,12 +478,12 @@ def _stringify_py36(annotation: Any, smartref: bool = False, show_typing: bool = elif annotation.__origin__ == Generator: # type: ignore params = annotation.__args__ # type: ignore else: # typing.Callable - args = ', '.join(stringify(arg, smartref, show_typing) for arg + args = ', '.join(stringify(arg, mode) for arg in annotation.__args__[:-1]) # type: ignore result = stringify(annotation.__args__[-1]) # type: ignore return '%s%s[[%s], %s]' % (modprefix, qualname, args, result) if params is not None: - param_str = ', '.join(stringify(p, smartref, show_typing) for p in params) + param_str = ', '.join(stringify(p, mode) for p in params) return '%s%s[%s]' % (modprefix, qualname, param_str) elif (hasattr(annotation, '__origin__') and annotation.__origin__ is typing.Union): @@ -488,13 +491,12 @@ def _stringify_py36(annotation: Any, smartref: bool = False, show_typing: bool = if params is not None: if len(params) > 1 and params[-1] is NoneType: if len(params) > 2: - param_str = ", ".join(stringify(p, smartref, show_typing) for p - in params[:-1]) + param_str = ", ".join(stringify(p, mode) for p in params[:-1]) return '%sOptional[%sUnion[%s]]' % (modprefix, modprefix, param_str) else: - return '%sOptional[%s]' % (modprefix, stringify(params[0])) + return '%sOptional[%s]' % (modprefix, stringify(params[0], mode)) else: - param_str = ', '.join(stringify(p, smartref, show_typing) for p in params) + param_str = ', '.join(stringify(p, mode) for p in params) return '%sUnion[%s]' % (modprefix, param_str) return modprefix + qualname diff --git a/tests/test_util_inspect.py b/tests/test_util_inspect.py index 25709a37f61..8662fab12ef 100644 --- a/tests/test_util_inspect.py +++ b/tests/test_util_inspect.py @@ -162,8 +162,10 @@ def test_signature_annotations(): # TypeVars and generic types with TypeVars sig = inspect.signature(f2) if sys.version_info < (3, 7): - assert stringify_signature(sig) == ('(x: typing.List[T], y: typing.List[T_co], z: T) ' - '-> typing.List[T_contra]') + assert stringify_signature(sig) == ('(x: typing.List[typing.T],' + ' y: typing.List[typing.T_co],' + ' z: typing.T' + ') -> typing.List[typing.T_contra]') else: assert stringify_signature(sig) == ('(x: typing.List[tests.typing_test_data.T],' ' y: typing.List[tests.typing_test_data.T_co],' diff --git a/tests/test_util_typing.py b/tests/test_util_typing.py index f17221458e7..9ee217f96bb 100644 --- a/tests/test_util_typing.py +++ b/tests/test_util_typing.py @@ -178,181 +178,181 @@ def test_restify_mock(): def test_stringify(): - assert stringify(int, False) == "int" - assert stringify(int, True) == "int" + assert stringify(int) == "int" + assert stringify(int, "smart") == "int" - assert stringify(str, False) == "str" - assert stringify(str, True) == "str" + assert stringify(str) == "str" + assert stringify(str, "smart") == "str" - assert stringify(None, False) == "None" - assert stringify(None, True) == "None" + assert stringify(None) == "None" + assert stringify(None, "smart") == "None" - assert stringify(Integral, False) == "numbers.Integral" - assert stringify(Integral, True) == "~numbers.Integral" + assert stringify(Integral) == "numbers.Integral" + assert stringify(Integral, "smart") == "~numbers.Integral" - assert stringify(Struct, False) == "struct.Struct" - assert stringify(Struct, True) == "~struct.Struct" + assert stringify(Struct) == "struct.Struct" + assert stringify(Struct, "smart") == "~struct.Struct" - assert stringify(TracebackType, False) == "types.TracebackType" - assert stringify(TracebackType, True) == "~types.TracebackType" + assert stringify(TracebackType) == "types.TracebackType" + assert stringify(TracebackType, "smart") == "~types.TracebackType" - assert stringify(Any, False) == "Any" - assert stringify(Any, False, True) == "typing.Any" - assert stringify(Any, True) == "~typing.Any" + assert stringify(Any) == "Any" + assert stringify(Any, "fully-qualified") == "typing.Any" + assert stringify(Any, "smart") == "~typing.Any" def test_stringify_type_hints_containers(): - assert stringify(List, False) == "List" - assert stringify(List, False, True) == "typing.List" - assert stringify(List, True) == "~typing.List" + assert stringify(List) == "List" + assert stringify(List, "fully-qualified") == "typing.List" + assert stringify(List, "smart") == "~typing.List" - assert stringify(Dict, False) == "Dict" - assert stringify(Dict, False, True) == "typing.Dict" - assert stringify(Dict, True) == "~typing.Dict" + assert stringify(Dict) == "Dict" + assert stringify(Dict, "fully-qualified") == "typing.Dict" + assert stringify(Dict, "smart") == "~typing.Dict" - assert stringify(List[int], False) == "List[int]" - assert stringify(List[int], False, True) == "typing.List[int]" - assert stringify(List[int], True) == "~typing.List[int]" + assert stringify(List[int]) == "List[int]" + assert stringify(List[int], "fully-qualified") == "typing.List[int]" + assert stringify(List[int], "smart") == "~typing.List[int]" - assert stringify(List[str], False) == "List[str]" - assert stringify(List[str], False, True) == "typing.List[str]" - assert stringify(List[str], True) == "~typing.List[str]" + assert stringify(List[str]) == "List[str]" + assert stringify(List[str], "fully-qualified") == "typing.List[str]" + assert stringify(List[str], "smart") == "~typing.List[str]" - assert stringify(Dict[str, float], False) == "Dict[str, float]" - assert stringify(Dict[str, float], False, True) == "typing.Dict[str, float]" - assert stringify(Dict[str, float], True) == "~typing.Dict[str, float]" + assert stringify(Dict[str, float]) == "Dict[str, float]" + assert stringify(Dict[str, float], "fully-qualified") == "typing.Dict[str, float]" + assert stringify(Dict[str, float], "smart") == "~typing.Dict[str, float]" - assert stringify(Tuple[str, str, str], False) == "Tuple[str, str, str]" - assert stringify(Tuple[str, str, str], False, True) == "typing.Tuple[str, str, str]" - assert stringify(Tuple[str, str, str], True) == "~typing.Tuple[str, str, str]" + assert stringify(Tuple[str, str, str]) == "Tuple[str, str, str]" + assert stringify(Tuple[str, str, str], "fully-qualified") == "typing.Tuple[str, str, str]" + assert stringify(Tuple[str, str, str], "smart") == "~typing.Tuple[str, str, str]" - assert stringify(Tuple[str, ...], False) == "Tuple[str, ...]" - assert stringify(Tuple[str, ...], False, True) == "typing.Tuple[str, ...]" - assert stringify(Tuple[str, ...], True) == "~typing.Tuple[str, ...]" + assert stringify(Tuple[str, ...]) == "Tuple[str, ...]" + assert stringify(Tuple[str, ...], "fully-qualified") == "typing.Tuple[str, ...]" + assert stringify(Tuple[str, ...], "smart") == "~typing.Tuple[str, ...]" - assert stringify(Tuple[()], False) == "Tuple[()]" - assert stringify(Tuple[()], False, True) == "typing.Tuple[()]" - assert stringify(Tuple[()], True) == "~typing.Tuple[()]" + assert stringify(Tuple[()]) == "Tuple[()]" + assert stringify(Tuple[()], "fully-qualified") == "typing.Tuple[()]" + assert stringify(Tuple[()], "smart") == "~typing.Tuple[()]" - assert stringify(List[Dict[str, Tuple]], False) == "List[Dict[str, Tuple]]" - assert stringify(List[Dict[str, Tuple]], False, True) == "typing.List[typing.Dict[str, typing.Tuple]]" - assert stringify(List[Dict[str, Tuple]], True) == "~typing.List[~typing.Dict[str, ~typing.Tuple]]" + assert stringify(List[Dict[str, Tuple]]) == "List[Dict[str, Tuple]]" + assert stringify(List[Dict[str, Tuple]], "fully-qualified") == "typing.List[typing.Dict[str, typing.Tuple]]" + assert stringify(List[Dict[str, Tuple]], "smart") == "~typing.List[~typing.Dict[str, ~typing.Tuple]]" - assert stringify(MyList[Tuple[int, int]], False) == "tests.test_util_typing.MyList[Tuple[int, int]]" - assert stringify(MyList[Tuple[int, int]], False, True) == "tests.test_util_typing.MyList[typing.Tuple[int, int]]" - assert stringify(MyList[Tuple[int, int]], True) == "~tests.test_util_typing.MyList[~typing.Tuple[int, int]]" + assert stringify(MyList[Tuple[int, int]]) == "tests.test_util_typing.MyList[Tuple[int, int]]" + assert stringify(MyList[Tuple[int, int]], "fully-qualified") == "tests.test_util_typing.MyList[typing.Tuple[int, int]]" + assert stringify(MyList[Tuple[int, int]], "smart") == "~tests.test_util_typing.MyList[~typing.Tuple[int, int]]" - assert stringify(Generator[None, None, None], False) == "Generator[None, None, None]" - assert stringify(Generator[None, None, None], False, True) == "typing.Generator[None, None, None]" - assert stringify(Generator[None, None, None], True) == "~typing.Generator[None, None, None]" + assert stringify(Generator[None, None, None]) == "Generator[None, None, None]" + assert stringify(Generator[None, None, None], "fully-qualified") == "typing.Generator[None, None, None]" + assert stringify(Generator[None, None, None], "smart") == "~typing.Generator[None, None, None]" @pytest.mark.skipif(sys.version_info < (3, 9), reason='python 3.9+ is required.') def test_stringify_type_hints_pep_585(): - assert stringify(list[int], False) == "list[int]" - assert stringify(list[int], True) == "list[int]" + assert stringify(list[int]) == "list[int]" + assert stringify(list[int], "smart") == "list[int]" - assert stringify(list[str], False) == "list[str]" - assert stringify(list[str], True) == "list[str]" + assert stringify(list[str]) == "list[str]" + assert stringify(list[str], "smart") == "list[str]" - assert stringify(dict[str, float], False) == "dict[str, float]" - assert stringify(dict[str, float], True) == "dict[str, float]" + assert stringify(dict[str, float]) == "dict[str, float]" + assert stringify(dict[str, float], "smart") == "dict[str, float]" - assert stringify(tuple[str, str, str], False) == "tuple[str, str, str]" - assert stringify(tuple[str, str, str], True) == "tuple[str, str, str]" + assert stringify(tuple[str, str, str]) == "tuple[str, str, str]" + assert stringify(tuple[str, str, str], "smart") == "tuple[str, str, str]" - assert stringify(tuple[str, ...], False) == "tuple[str, ...]" - assert stringify(tuple[str, ...], True) == "tuple[str, ...]" + assert stringify(tuple[str, ...]) == "tuple[str, ...]" + assert stringify(tuple[str, ...], "smart") == "tuple[str, ...]" - assert stringify(tuple[()], False) == "tuple[()]" - assert stringify(tuple[()], True) == "tuple[()]" + assert stringify(tuple[()]) == "tuple[()]" + assert stringify(tuple[()], "smart") == "tuple[()]" - assert stringify(list[dict[str, tuple]], False) == "list[dict[str, tuple]]" - assert stringify(list[dict[str, tuple]], True) == "list[dict[str, tuple]]" + assert stringify(list[dict[str, tuple]]) == "list[dict[str, tuple]]" + assert stringify(list[dict[str, tuple]], "smart") == "list[dict[str, tuple]]" - assert stringify(type[int], False) == "type[int]" - assert stringify(type[int], True) == "type[int]" + assert stringify(type[int]) == "type[int]" + assert stringify(type[int], "smart") == "type[int]" @pytest.mark.skipif(sys.version_info < (3, 9), reason='python 3.9+ is required.') def test_stringify_Annotated(): from typing import Annotated # type: ignore - assert stringify(Annotated[str, "foo", "bar"], False) == "str" # NOQA - assert stringify(Annotated[str, "foo", "bar"], True) == "str" # NOQA + assert stringify(Annotated[str, "foo", "bar"]) == "str" # NOQA + assert stringify(Annotated[str, "foo", "bar"], "smart") == "str" # NOQA def test_stringify_type_hints_string(): - assert stringify("int", False) == "int" - assert stringify("int", True) == "int" + assert stringify("int") == "int" + assert stringify("int", "smart") == "int" - assert stringify("str", False) == "str" - assert stringify("str", True) == "str" + assert stringify("str") == "str" + assert stringify("str", "smart") == "str" - assert stringify(List["int"], False) == "List[int]" - assert stringify(List["int"], True) == "~typing.List[int]" + assert stringify(List["int"]) == "List[int]" + assert stringify(List["int"], "smart") == "~typing.List[int]" - assert stringify("Tuple[str]", False) == "Tuple[str]" - assert stringify("Tuple[str]", True) == "Tuple[str]" + assert stringify("Tuple[str]") == "Tuple[str]" + assert stringify("Tuple[str]", "smart") == "Tuple[str]" - assert stringify("unknown", False) == "unknown" - assert stringify("unknown", True) == "unknown" + assert stringify("unknown") == "unknown" + assert stringify("unknown", "smart") == "unknown" def test_stringify_type_hints_Callable(): - assert stringify(Callable, False) == "Callable" - assert stringify(Callable, False, True) == "typing.Callable" - assert stringify(Callable, True) == "~typing.Callable" + assert stringify(Callable) == "Callable" + assert stringify(Callable, "fully-qualified") == "typing.Callable" + assert stringify(Callable, "smart") == "~typing.Callable" if sys.version_info >= (3, 7): - assert stringify(Callable[[str], int], False) == "Callable[[str], int]" - assert stringify(Callable[[str], int], False, True) == "typing.Callable[[str], int]" - assert stringify(Callable[[str], int], True) == "~typing.Callable[[str], int]" + assert stringify(Callable[[str], int]) == "Callable[[str], int]" + assert stringify(Callable[[str], int], "fully-qualified") == "typing.Callable[[str], int]" + assert stringify(Callable[[str], int], "smart") == "~typing.Callable[[str], int]" - assert stringify(Callable[..., int], False) == "Callable[[...], int]" - assert stringify(Callable[..., int], False, True) == "typing.Callable[[...], int]" - assert stringify(Callable[..., int], True) == "~typing.Callable[[...], int]" + assert stringify(Callable[..., int]) == "Callable[[...], int]" + assert stringify(Callable[..., int], "fully-qualified") == "typing.Callable[[...], int]" + assert stringify(Callable[..., int], "smart") == "~typing.Callable[[...], int]" else: - assert stringify(Callable[[str], int], False) == "Callable[str, int]" - assert stringify(Callable[[str], int], False, True) == "typing.Callable[str, int]" - assert stringify(Callable[[str], int], True) == "~typing.Callable[str, int]" + assert stringify(Callable[[str], int]) == "Callable[str, int]" + assert stringify(Callable[[str], int], "fully-qualified") == "typing.Callable[str, int]" + assert stringify(Callable[[str], int], "smart") == "~typing.Callable[str, int]" - assert stringify(Callable[..., int], False) == "Callable[..., int]" - assert stringify(Callable[..., int], False, True) == "typing.Callable[..., int]" - assert stringify(Callable[..., int], True) == "~typing.Callable[..., int]" + assert stringify(Callable[..., int]) == "Callable[..., int]" + assert stringify(Callable[..., int], "fully-qualified") == "typing.Callable[..., int]" + assert stringify(Callable[..., int], "smart") == "~typing.Callable[..., int]" def test_stringify_type_hints_Union(): - assert stringify(Optional[int], False) == "Optional[int]" - assert stringify(Optional[int], False, True) == "typing.Optional[int]" - assert stringify(Optional[int], True) == "~typing.Optional[int]" + assert stringify(Optional[int]) == "Optional[int]" + assert stringify(Optional[int], "fully-qualified") == "typing.Optional[int]" + assert stringify(Optional[int], "smart") == "~typing.Optional[int]" - assert stringify(Union[str, None], False) == "Optional[str]" - assert stringify(Union[str, None], False, True) == "typing.Optional[str]" - assert stringify(Union[str, None], True) == "~typing.Optional[str]" + assert stringify(Union[str, None]) == "Optional[str]" + assert stringify(Union[str, None], "fully-qualified") == "typing.Optional[str]" + assert stringify(Union[str, None], "smart") == "~typing.Optional[str]" - assert stringify(Union[int, str], False) == "Union[int, str]" - assert stringify(Union[int, str], False, True) == "typing.Union[int, str]" - assert stringify(Union[int, str], True) == "~typing.Union[int, str]" + assert stringify(Union[int, str]) == "Union[int, str]" + assert stringify(Union[int, str], "fully-qualified") == "typing.Union[int, str]" + assert stringify(Union[int, str], "smart") == "~typing.Union[int, str]" if sys.version_info >= (3, 7): - assert stringify(Union[int, Integral], False) == "Union[int, numbers.Integral]" - assert stringify(Union[int, Integral], False, True) == "typing.Union[int, numbers.Integral]" - assert stringify(Union[int, Integral], True) == "~typing.Union[int, ~numbers.Integral]" + assert stringify(Union[int, Integral]) == "Union[int, numbers.Integral]" + assert stringify(Union[int, Integral], "fully-qualified") == "typing.Union[int, numbers.Integral]" + assert stringify(Union[int, Integral], "smart") == "~typing.Union[int, ~numbers.Integral]" - assert (stringify(Union[MyClass1, MyClass2], False) == + assert (stringify(Union[MyClass1, MyClass2]) == "Union[tests.test_util_typing.MyClass1, tests.test_util_typing.]") - assert (stringify(Union[MyClass1, MyClass2], False, True) == + assert (stringify(Union[MyClass1, MyClass2], "fully-qualified") == "typing.Union[tests.test_util_typing.MyClass1, tests.test_util_typing.]") - assert (stringify(Union[MyClass1, MyClass2], True) == + assert (stringify(Union[MyClass1, MyClass2], "smart") == "~typing.Union[~tests.test_util_typing.MyClass1, ~tests.test_util_typing.]") else: - assert stringify(Union[int, Integral], False) == "numbers.Integral" - assert stringify(Union[int, Integral], False, True) == "numbers.Integral" - assert stringify(Union[int, Integral], True) == "~numbers.Integral" + assert stringify(Union[int, Integral]) == "numbers.Integral" + assert stringify(Union[int, Integral], "fully-qualified") == "numbers.Integral" + assert stringify(Union[int, Integral], "smart") == "~numbers.Integral" - assert stringify(Union[MyClass1, MyClass2], False) == "tests.test_util_typing.MyClass1" - assert stringify(Union[MyClass1, MyClass2], False, True) == "tests.test_util_typing.MyClass1" - assert stringify(Union[MyClass1, MyClass2], True) == "~tests.test_util_typing.MyClass1" + assert stringify(Union[MyClass1, MyClass2]) == "tests.test_util_typing.MyClass1" + assert stringify(Union[MyClass1, MyClass2], "fully-qualified") == "tests.test_util_typing.MyClass1" + assert stringify(Union[MyClass1, MyClass2], "smart") == "~tests.test_util_typing.MyClass1" def test_stringify_type_hints_typevars(): @@ -361,84 +361,84 @@ def test_stringify_type_hints_typevars(): T_contra = TypeVar('T_contra', contravariant=True) if sys.version_info < (3, 7): - assert stringify(T, False) == "T" - assert stringify(T, True) == "T" + assert stringify(T) == "T" + assert stringify(T, "smart") == "T" - assert stringify(T_co, False) == "T_co" - assert stringify(T_co, True) == "T_co" + assert stringify(T_co) == "T_co" + assert stringify(T_co, "smart") == "T_co" - assert stringify(T_contra, False) == "T_contra" - assert stringify(T_contra, True) == "T_contra" + assert stringify(T_contra) == "T_contra" + assert stringify(T_contra, "smart") == "T_contra" - assert stringify(List[T], False) == "List[T]" - assert stringify(List[T], True) == "~typing.List[T]" + assert stringify(List[T]) == "List[T]" + assert stringify(List[T], "smart") == "~typing.List[T]" else: - assert stringify(T, False) == "tests.test_util_typing.T" - assert stringify(T, True) == "~tests.test_util_typing.T" + assert stringify(T) == "tests.test_util_typing.T" + assert stringify(T, "smart") == "~tests.test_util_typing.T" - assert stringify(T_co, False) == "tests.test_util_typing.T_co" - assert stringify(T_co, True) == "~tests.test_util_typing.T_co" + assert stringify(T_co) == "tests.test_util_typing.T_co" + assert stringify(T_co, "smart") == "~tests.test_util_typing.T_co" - assert stringify(T_contra, False) == "tests.test_util_typing.T_contra" - assert stringify(T_contra, True) == "~tests.test_util_typing.T_contra" + assert stringify(T_contra) == "tests.test_util_typing.T_contra" + assert stringify(T_contra, "smart") == "~tests.test_util_typing.T_contra" - assert stringify(List[T], False) == "List[tests.test_util_typing.T]" - assert stringify(List[T], True) == "~typing.List[~tests.test_util_typing.T]" + assert stringify(List[T]) == "List[tests.test_util_typing.T]" + assert stringify(List[T], "smart") == "~typing.List[~tests.test_util_typing.T]" if sys.version_info >= (3, 10): - assert stringify(MyInt, False) == "tests.test_util_typing.MyInt" - assert stringify(MyInt, True) == "~tests.test_util_typing.MyInt" + assert stringify(MyInt) == "tests.test_util_typing.MyInt" + assert stringify(MyInt, "smart") == "~tests.test_util_typing.MyInt" else: - assert stringify(MyInt, False) == "MyInt" - assert stringify(MyInt, True) == "MyInt" + assert stringify(MyInt) == "MyInt" + assert stringify(MyInt, "smart") == "MyInt" def test_stringify_type_hints_custom_class(): - assert stringify(MyClass1, False) == "tests.test_util_typing.MyClass1" - assert stringify(MyClass1, True) == "~tests.test_util_typing.MyClass1" + assert stringify(MyClass1) == "tests.test_util_typing.MyClass1" + assert stringify(MyClass1, "smart") == "~tests.test_util_typing.MyClass1" - assert stringify(MyClass2, False) == "tests.test_util_typing." - assert stringify(MyClass2, True) == "~tests.test_util_typing." + assert stringify(MyClass2) == "tests.test_util_typing." + assert stringify(MyClass2, "smart") == "~tests.test_util_typing." def test_stringify_type_hints_alias(): MyStr = str MyTuple = Tuple[str, str] - assert stringify(MyStr, False) == "str" - assert stringify(MyStr, True) == "str" + assert stringify(MyStr) == "str" + assert stringify(MyStr, "smart") == "str" - assert stringify(MyTuple, False) == "Tuple[str, str]" # type: ignore - assert stringify(MyTuple, True) == "~typing.Tuple[str, str]" # type: ignore + assert stringify(MyTuple) == "Tuple[str, str]" # type: ignore + assert stringify(MyTuple, "smart") == "~typing.Tuple[str, str]" # type: ignore @pytest.mark.skipif(sys.version_info < (3, 8), reason='python 3.8+ is required.') def test_stringify_type_Literal(): from typing import Literal # type: ignore - assert stringify(Literal[1, "2", "\r"], False) == "Literal[1, '2', '\\r']" - assert stringify(Literal[1, "2", "\r"], False, True) == "typing.Literal[1, '2', '\\r']" - assert stringify(Literal[1, "2", "\r"], True) == "~typing.Literal[1, '2', '\\r']" + assert stringify(Literal[1, "2", "\r"]) == "Literal[1, '2', '\\r']" + assert stringify(Literal[1, "2", "\r"], "fully-qualified") == "typing.Literal[1, '2', '\\r']" + assert stringify(Literal[1, "2", "\r"], "smart") == "~typing.Literal[1, '2', '\\r']" @pytest.mark.skipif(sys.version_info < (3, 10), reason='python 3.10+ is required.') def test_stringify_type_union_operator(): - assert stringify(int | None, False) == "int | None" # type: ignore - assert stringify(int | None, True) == "int | None" # type: ignore + assert stringify(int | None) == "int | None" # type: ignore + assert stringify(int | None, "smart") == "int | None" # type: ignore - assert stringify(int | str, False) == "int | str" # type: ignore - assert stringify(int | str, True) == "int | str" # type: ignore + assert stringify(int | str) == "int | str" # type: ignore + assert stringify(int | str, "smart") == "int | str" # type: ignore - assert stringify(int | str | None, False) == "int | str | None" # type: ignore - assert stringify(int | str | None, True) == "int | str | None" # type: ignore + assert stringify(int | str | None) == "int | str | None" # type: ignore + assert stringify(int | str | None, "smart") == "int | str | None" # type: ignore def test_stringify_broken_type_hints(): - assert stringify(BrokenType, False) == 'tests.test_util_typing.BrokenType' - assert stringify(BrokenType, True) == '~tests.test_util_typing.BrokenType' + assert stringify(BrokenType) == 'tests.test_util_typing.BrokenType' + assert stringify(BrokenType, "smart") == '~tests.test_util_typing.BrokenType' def test_stringify_mock(): with mock(['unknown']): import unknown - assert stringify(unknown.secret.Class, False) == 'unknown.secret.Class' - assert stringify(unknown.secret.Class, True) == 'unknown.secret.Class' + assert stringify(unknown.secret.Class) == 'unknown.secret.Class' + assert stringify(unknown.secret.Class, "smart") == 'unknown.secret.Class' From 7da429924ed0d625afc820abe8ec41d1d0f7e2cb Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sun, 26 Dec 2021 20:34:03 +0900 Subject: [PATCH 6/6] Fix testcase in py36 case --- tests/test_ext_autodoc_configs.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/tests/test_ext_autodoc_configs.py b/tests/test_ext_autodoc_configs.py index 899bf9ff024..e84bcf76d2b 100644 --- a/tests/test_ext_autodoc_configs.py +++ b/tests/test_ext_autodoc_configs.py @@ -1146,11 +1146,6 @@ def test_autodoc_typehints_description_and_type_aliases(app): @pytest.mark.sphinx('html', testroot='ext-autodoc', confoverrides={'autodoc_unqualified_typehints': True}) def test_autodoc_unqualified_typehints(app): - if sys.version_info < (3, 7): - Any = 'Any' - else: - Any = '~typing.Any' - options = {"members": None, "undoc-members": None} actual = do_autodoc(app, 'module', 'target.typehints', options) @@ -1164,7 +1159,7 @@ def test_autodoc_unqualified_typehints(app): ' :type: int', '', '', - '.. py:class:: Math(s: str, o: ~typing.Optional[%s] = None)' % Any, + '.. py:class:: Math(s: str, o: ~typing.Optional[~typing.Any] = None)', ' :module: target.typehints', '', '',