1""" 2 test_util_typing 3 ~~~~~~~~~~~~~~~~ 4 5 Tests util.typing functions. 6 7 :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. 8 :license: BSD, see LICENSE for details. 9""" 10 11import sys 12from numbers import Integral 13from struct import Struct 14from typing import (Any, Callable, Dict, Generator, List, NewType, Optional, Tuple, TypeVar, 15 Union) 16 17import pytest 18 19from sphinx.util.typing import restify, stringify 20 21 22class MyClass1: 23 pass 24 25 26class MyClass2(MyClass1): 27 __qualname__ = '<MyClass2>' 28 29 30T = TypeVar('T') 31MyInt = NewType('MyInt', int) 32 33 34class MyList(List[T]): 35 pass 36 37 38class BrokenType: 39 __args__ = int 40 41 42def test_restify(): 43 assert restify(int) == ":class:`int`" 44 assert restify(str) == ":class:`str`" 45 assert restify(None) == ":obj:`None`" 46 assert restify(Integral) == ":class:`numbers.Integral`" 47 assert restify(Struct) == ":class:`struct.Struct`" 48 assert restify(Any) == ":obj:`Any`" 49 50 51def test_restify_type_hints_containers(): 52 assert restify(List) == ":class:`List`" 53 assert restify(Dict) == ":class:`Dict`" 54 assert restify(List[int]) == ":class:`List`\\ [:class:`int`]" 55 assert restify(List[str]) == ":class:`List`\\ [:class:`str`]" 56 assert restify(Dict[str, float]) == ":class:`Dict`\\ [:class:`str`, :class:`float`]" 57 assert restify(Tuple[str, str, str]) == ":class:`Tuple`\\ [:class:`str`, :class:`str`, :class:`str`]" 58 assert restify(Tuple[str, ...]) == ":class:`Tuple`\\ [:class:`str`, ...]" 59 assert restify(List[Dict[str, Tuple]]) == ":class:`List`\\ [:class:`Dict`\\ [:class:`str`, :class:`Tuple`]]" 60 assert restify(MyList[Tuple[int, int]]) == ":class:`tests.test_util_typing.MyList`\\ [:class:`Tuple`\\ [:class:`int`, :class:`int`]]" 61 assert restify(Generator[None, None, None]) == ":class:`Generator`\\ [:obj:`None`, :obj:`None`, :obj:`None`]" 62 63 64def test_restify_type_hints_Callable(): 65 assert restify(Callable) == ":class:`Callable`" 66 67 if sys.version_info >= (3, 7): 68 assert restify(Callable[[str], int]) == ":class:`Callable`\\ [[:class:`str`], :class:`int`]" 69 assert restify(Callable[..., int]) == ":class:`Callable`\\ [[...], :class:`int`]" 70 else: 71 assert restify(Callable[[str], int]) == ":class:`Callable`\\ [:class:`str`, :class:`int`]" 72 assert restify(Callable[..., int]) == ":class:`Callable`\\ [..., :class:`int`]" 73 74 75def test_restify_type_hints_Union(): 76 assert restify(Optional[int]) == ":obj:`Optional`\\ [:class:`int`]" 77 assert restify(Union[str, None]) == ":obj:`Optional`\\ [:class:`str`]" 78 assert restify(Union[int, str]) == ":obj:`Union`\\ [:class:`int`, :class:`str`]" 79 80 if sys.version_info >= (3, 7): 81 assert restify(Union[int, Integral]) == ":obj:`Union`\\ [:class:`int`, :class:`numbers.Integral`]" 82 assert (restify(Union[MyClass1, MyClass2]) == 83 ":obj:`Union`\\ [:class:`tests.test_util_typing.MyClass1`, :class:`tests.test_util_typing.<MyClass2>`]") 84 else: 85 assert restify(Union[int, Integral]) == ":class:`numbers.Integral`" 86 assert restify(Union[MyClass1, MyClass2]) == ":class:`tests.test_util_typing.MyClass1`" 87 88 89@pytest.mark.skipif(sys.version_info < (3, 7), reason='python 3.7+ is required.') 90def test_restify_type_hints_typevars(): 91 T = TypeVar('T') 92 T_co = TypeVar('T_co', covariant=True) 93 T_contra = TypeVar('T_contra', contravariant=True) 94 95 assert restify(T) == ":obj:`tests.test_util_typing.T`" 96 assert restify(T_co) == ":obj:`tests.test_util_typing.T_co`" 97 assert restify(T_contra) == ":obj:`tests.test_util_typing.T_contra`" 98 assert restify(List[T]) == ":class:`List`\\ [:obj:`tests.test_util_typing.T`]" 99 assert restify(MyInt) == ":class:`MyInt`" 100 101 102def test_restify_type_hints_custom_class(): 103 assert restify(MyClass1) == ":class:`tests.test_util_typing.MyClass1`" 104 assert restify(MyClass2) == ":class:`tests.test_util_typing.<MyClass2>`" 105 106 107def test_restify_type_hints_alias(): 108 MyStr = str 109 MyTuple = Tuple[str, str] 110 assert restify(MyStr) == ":class:`str`" 111 assert restify(MyTuple) == ":class:`Tuple`\\ [:class:`str`, :class:`str`]" # type: ignore 112 113 114@pytest.mark.skipif(sys.version_info < (3, 7), reason='python 3.7+ is required.') 115def test_restify_type_ForwardRef(): 116 from typing import ForwardRef # type: ignore 117 assert restify(ForwardRef("myint")) == ":class:`myint`" 118 119 120@pytest.mark.skipif(sys.version_info < (3, 10), reason='python 3.10+ is required.') 121def test_restify_type_union_operator(): 122 assert restify(int | None) == "Optional[:class:`int`]" # type: ignore 123 assert restify(int | str) == ":class:`int` | :class:`str`" # type: ignore 124 assert restify(int | str | None) == "Optional[:class:`int` | :class:`str`]" # type: ignore 125 126 127def test_restify_broken_type_hints(): 128 assert restify(BrokenType) == ':class:`tests.test_util_typing.BrokenType`' 129 130 131def test_stringify(): 132 assert stringify(int) == "int" 133 assert stringify(str) == "str" 134 assert stringify(None) == "None" 135 assert stringify(Integral) == "numbers.Integral" 136 assert restify(Struct) == ":class:`struct.Struct`" 137 assert stringify(Any) == "Any" 138 139 140def test_stringify_type_hints_containers(): 141 assert stringify(List) == "List" 142 assert stringify(Dict) == "Dict" 143 assert stringify(List[int]) == "List[int]" 144 assert stringify(List[str]) == "List[str]" 145 assert stringify(Dict[str, float]) == "Dict[str, float]" 146 assert stringify(Tuple[str, str, str]) == "Tuple[str, str, str]" 147 assert stringify(Tuple[str, ...]) == "Tuple[str, ...]" 148 assert stringify(List[Dict[str, Tuple]]) == "List[Dict[str, Tuple]]" 149 assert stringify(MyList[Tuple[int, int]]) == "tests.test_util_typing.MyList[Tuple[int, int]]" 150 assert stringify(Generator[None, None, None]) == "Generator[None, None, None]" 151 152 153@pytest.mark.skipif(sys.version_info < (3, 9), reason='python 3.9+ is required.') 154def test_stringify_Annotated(): 155 from typing import Annotated # type: ignore 156 assert stringify(Annotated[str, "foo", "bar"]) == "str" # NOQA 157 158 159def test_stringify_type_hints_string(): 160 assert stringify("int") == "int" 161 assert stringify("str") == "str" 162 assert stringify(List["int"]) == "List[int]" 163 assert stringify("Tuple[str]") == "Tuple[str]" 164 assert stringify("unknown") == "unknown" 165 166 167def test_stringify_type_hints_Callable(): 168 assert stringify(Callable) == "Callable" 169 170 if sys.version_info >= (3, 7): 171 assert stringify(Callable[[str], int]) == "Callable[[str], int]" 172 assert stringify(Callable[..., int]) == "Callable[[...], int]" 173 else: 174 assert stringify(Callable[[str], int]) == "Callable[str, int]" 175 assert stringify(Callable[..., int]) == "Callable[..., int]" 176 177 178def test_stringify_type_hints_Union(): 179 assert stringify(Optional[int]) == "Optional[int]" 180 assert stringify(Union[str, None]) == "Optional[str]" 181 assert stringify(Union[int, str]) == "Union[int, str]" 182 183 if sys.version_info >= (3, 7): 184 assert stringify(Union[int, Integral]) == "Union[int, numbers.Integral]" 185 assert (stringify(Union[MyClass1, MyClass2]) == 186 "Union[tests.test_util_typing.MyClass1, tests.test_util_typing.<MyClass2>]") 187 else: 188 assert stringify(Union[int, Integral]) == "numbers.Integral" 189 assert stringify(Union[MyClass1, MyClass2]) == "tests.test_util_typing.MyClass1" 190 191 192def test_stringify_type_hints_typevars(): 193 T = TypeVar('T') 194 T_co = TypeVar('T_co', covariant=True) 195 T_contra = TypeVar('T_contra', contravariant=True) 196 197 assert stringify(T) == "T" 198 assert stringify(T_co) == "T_co" 199 assert stringify(T_contra) == "T_contra" 200 assert stringify(List[T]) == "List[T]" 201 assert stringify(MyInt) == "MyInt" 202 203 204def test_stringify_type_hints_custom_class(): 205 assert stringify(MyClass1) == "tests.test_util_typing.MyClass1" 206 assert stringify(MyClass2) == "tests.test_util_typing.<MyClass2>" 207 208 209def test_stringify_type_hints_alias(): 210 MyStr = str 211 MyTuple = Tuple[str, str] 212 assert stringify(MyStr) == "str" 213 assert stringify(MyTuple) == "Tuple[str, str]" # type: ignore 214 215 216@pytest.mark.skipif(sys.version_info < (3, 10), reason='python 3.10+ is required.') 217def test_stringify_type_union_operator(): 218 assert stringify(int | None) == "Optional[int]" # type: ignore 219 assert stringify(int | str) == "int | str" # type: ignore 220 assert stringify(int | str | None) == "Optional[int | str]" # type: ignore 221 222 223def test_stringify_broken_type_hints(): 224 assert stringify(BrokenType) == 'tests.test_util_typing.BrokenType' 225