Coverage for /opt/homebrew/lib/python3.11/site-packages/_pytest/compat.py: 62%

161 statements  

« prev     ^ index     » next       coverage.py v7.2.3, created at 2023-05-04 13:14 +0700

1"""Python version compatibility code.""" 

2import enum 

3import functools 

4import inspect 

5import os 

6import sys 

7from inspect import Parameter 

8from inspect import signature 

9from pathlib import Path 

10from typing import Any 

11from typing import Callable 

12from typing import Generic 

13from typing import NoReturn 

14from typing import Optional 

15from typing import Tuple 

16from typing import TYPE_CHECKING 

17from typing import TypeVar 

18from typing import Union 

19 

20import attr 

21 

22import py 

23 

24# fmt: off 

25# Workaround for https://github.com/sphinx-doc/sphinx/issues/10351. 

26# If `overload` is imported from `compat` instead of from `typing`, 

27# Sphinx doesn't recognize it as `overload` and the API docs for 

28# overloaded functions look good again. But type checkers handle 

29# it fine. 

30# fmt: on 

31if True: 

32 from typing import overload as overload 

33 

34if TYPE_CHECKING: 

35 from typing_extensions import Final 

36 

37 

38_T = TypeVar("_T") 

39_S = TypeVar("_S") 

40 

41#: constant to prepare valuing pylib path replacements/lazy proxies later on 

42# intended for removal in pytest 8.0 or 9.0 

43 

44# fmt: off 

45# intentional space to create a fake difference for the verification 

46LEGACY_PATH = py.path. local 

47# fmt: on 

48 

49 

50def legacy_path(path: Union[str, "os.PathLike[str]"]) -> LEGACY_PATH: 

51 """Internal wrapper to prepare lazy proxies for legacy_path instances""" 

52 return LEGACY_PATH(path) 

53 

54 

55# fmt: off 

56# Singleton type for NOTSET, as described in: 

57# https://www.python.org/dev/peps/pep-0484/#support-for-singleton-types-in-unions 

58class NotSetType(enum.Enum): 

59 token = 0 

60NOTSET: "Final" = NotSetType.token # noqa: E305 

61# fmt: on 

62 

63if sys.version_info >= (3, 8): 

64 import importlib.metadata 

65 

66 importlib_metadata = importlib.metadata 

67else: 

68 import importlib_metadata as importlib_metadata # noqa: F401 

69 

70 

71def _format_args(func: Callable[..., Any]) -> str: 

72 return str(signature(func)) 

73 

74 

75def is_generator(func: object) -> bool: 

76 genfunc = inspect.isgeneratorfunction(func) 

77 return genfunc and not iscoroutinefunction(func) 

78 

79 

80def iscoroutinefunction(func: object) -> bool: 

81 """Return True if func is a coroutine function (a function defined with async 

82 def syntax, and doesn't contain yield), or a function decorated with 

83 @asyncio.coroutine. 

84 

85 Note: copied and modified from Python 3.5's builtin couroutines.py to avoid 

86 importing asyncio directly, which in turns also initializes the "logging" 

87 module as a side-effect (see issue #8). 

88 """ 

89 return inspect.iscoroutinefunction(func) or getattr(func, "_is_coroutine", False) 

90 

91 

92def is_async_function(func: object) -> bool: 

93 """Return True if the given function seems to be an async function or 

94 an async generator.""" 

95 return iscoroutinefunction(func) or inspect.isasyncgenfunction(func) 

96 

97 

98def getlocation(function, curdir: Optional[str] = None) -> str: 

99 function = get_real_func(function) 

100 fn = Path(inspect.getfile(function)) 

101 lineno = function.__code__.co_firstlineno 

102 if curdir is not None: 

103 try: 

104 relfn = fn.relative_to(curdir) 

105 except ValueError: 

106 pass 

107 else: 

108 return "%s:%d" % (relfn, lineno + 1) 

109 return "%s:%d" % (fn, lineno + 1) 

110 

111 

112def num_mock_patch_args(function) -> int: 

113 """Return number of arguments used up by mock arguments (if any).""" 

114 patchings = getattr(function, "patchings", None) 

115 if not patchings: 

116 return 0 

117 

118 mock_sentinel = getattr(sys.modules.get("mock"), "DEFAULT", object()) 

119 ut_mock_sentinel = getattr(sys.modules.get("unittest.mock"), "DEFAULT", object()) 

120 

121 return len( 

122 [ 

123 p 

124 for p in patchings 

125 if not p.attribute_name 

126 and (p.new is mock_sentinel or p.new is ut_mock_sentinel) 

127 ] 

128 ) 

129 

130 

131def getfuncargnames( 

132 function: Callable[..., Any], 

133 *, 

134 name: str = "", 

135 is_method: bool = False, 

136 cls: Optional[type] = None, 

137) -> Tuple[str, ...]: 

138 """Return the names of a function's mandatory arguments. 

139 

140 Should return the names of all function arguments that: 

141 * Aren't bound to an instance or type as in instance or class methods. 

142 * Don't have default values. 

143 * Aren't bound with functools.partial. 

144 * Aren't replaced with mocks. 

145 

146 The is_method and cls arguments indicate that the function should 

147 be treated as a bound method even though it's not unless, only in 

148 the case of cls, the function is a static method. 

149 

150 The name parameter should be the original name in which the function was collected. 

151 """ 

152 # TODO(RonnyPfannschmidt): This function should be refactored when we 

153 # revisit fixtures. The fixture mechanism should ask the node for 

154 # the fixture names, and not try to obtain directly from the 

155 # function object well after collection has occurred. 

156 

157 # The parameters attribute of a Signature object contains an 

158 # ordered mapping of parameter names to Parameter instances. This 

159 # creates a tuple of the names of the parameters that don't have 

160 # defaults. 

161 try: 

162 parameters = signature(function).parameters 

163 except (ValueError, TypeError) as e: 

164 from _pytest.outcomes import fail 

165 

166 fail( 

167 f"Could not determine arguments of {function!r}: {e}", 

168 pytrace=False, 

169 ) 

170 

171 arg_names = tuple( 

172 p.name 

173 for p in parameters.values() 

174 if ( 

175 p.kind is Parameter.POSITIONAL_OR_KEYWORD 

176 or p.kind is Parameter.KEYWORD_ONLY 

177 ) 

178 and p.default is Parameter.empty 

179 ) 

180 if not name: 

181 name = function.__name__ 

182 

183 # If this function should be treated as a bound method even though 

184 # it's passed as an unbound method or function, remove the first 

185 # parameter name. 

186 if is_method or ( 

187 # Not using `getattr` because we don't want to resolve the staticmethod. 

188 # Not using `cls.__dict__` because we want to check the entire MRO. 

189 cls 

190 and not isinstance( 

191 inspect.getattr_static(cls, name, default=None), staticmethod 

192 ) 

193 ): 

194 arg_names = arg_names[1:] 

195 # Remove any names that will be replaced with mocks. 

196 if hasattr(function, "__wrapped__"): 

197 arg_names = arg_names[num_mock_patch_args(function) :] 

198 return arg_names 

199 

200 

201def get_default_arg_names(function: Callable[..., Any]) -> Tuple[str, ...]: 

202 # Note: this code intentionally mirrors the code at the beginning of 

203 # getfuncargnames, to get the arguments which were excluded from its result 

204 # because they had default values. 

205 return tuple( 

206 p.name 

207 for p in signature(function).parameters.values() 

208 if p.kind in (Parameter.POSITIONAL_OR_KEYWORD, Parameter.KEYWORD_ONLY) 

209 and p.default is not Parameter.empty 

210 ) 

211 

212 

213_non_printable_ascii_translate_table = { 

214 i: f"\\x{i:02x}" for i in range(128) if i not in range(32, 127) 

215} 

216_non_printable_ascii_translate_table.update( 

217 {ord("\t"): "\\t", ord("\r"): "\\r", ord("\n"): "\\n"} 

218) 

219 

220 

221def _translate_non_printable(s: str) -> str: 

222 return s.translate(_non_printable_ascii_translate_table) 

223 

224 

225STRING_TYPES = bytes, str 

226 

227 

228def _bytes_to_ascii(val: bytes) -> str: 

229 return val.decode("ascii", "backslashreplace") 

230 

231 

232def ascii_escaped(val: Union[bytes, str]) -> str: 

233 r"""If val is pure ASCII, return it as an str, otherwise, escape 

234 bytes objects into a sequence of escaped bytes: 

235 

236 b'\xc3\xb4\xc5\xd6' -> r'\xc3\xb4\xc5\xd6' 

237 

238 and escapes unicode objects into a sequence of escaped unicode 

239 ids, e.g.: 

240 

241 r'4\nV\U00043efa\x0eMXWB\x1e\u3028\u15fd\xcd\U0007d944' 

242 

243 Note: 

244 The obvious "v.decode('unicode-escape')" will return 

245 valid UTF-8 unicode if it finds them in bytes, but we 

246 want to return escaped bytes for any byte, even if they match 

247 a UTF-8 string. 

248 """ 

249 if isinstance(val, bytes): 

250 ret = _bytes_to_ascii(val) 

251 else: 

252 ret = val.encode("unicode_escape").decode("ascii") 

253 return _translate_non_printable(ret) 

254 

255 

256@attr.s 

257class _PytestWrapper: 

258 """Dummy wrapper around a function object for internal use only. 

259 

260 Used to correctly unwrap the underlying function object when we are 

261 creating fixtures, because we wrap the function object ourselves with a 

262 decorator to issue warnings when the fixture function is called directly. 

263 """ 

264 

265 obj = attr.ib() 

266 

267 

268def get_real_func(obj): 

269 """Get the real function object of the (possibly) wrapped object by 

270 functools.wraps or functools.partial.""" 

271 start_obj = obj 

272 for i in range(100): 

273 # __pytest_wrapped__ is set by @pytest.fixture when wrapping the fixture function 

274 # to trigger a warning if it gets called directly instead of by pytest: we don't 

275 # want to unwrap further than this otherwise we lose useful wrappings like @mock.patch (#3774) 

276 new_obj = getattr(obj, "__pytest_wrapped__", None) 

277 if isinstance(new_obj, _PytestWrapper): 

278 obj = new_obj.obj 

279 break 

280 new_obj = getattr(obj, "__wrapped__", None) 

281 if new_obj is None: 

282 break 

283 obj = new_obj 

284 else: 

285 from _pytest._io.saferepr import saferepr 

286 

287 raise ValueError( 

288 ("could not find real function of {start}\nstopped at {current}").format( 

289 start=saferepr(start_obj), current=saferepr(obj) 

290 ) 

291 ) 

292 if isinstance(obj, functools.partial): 

293 obj = obj.func 

294 return obj 

295 

296 

297def get_real_method(obj, holder): 

298 """Attempt to obtain the real function object that might be wrapping 

299 ``obj``, while at the same time returning a bound method to ``holder`` if 

300 the original object was a bound method.""" 

301 try: 

302 is_method = hasattr(obj, "__func__") 

303 obj = get_real_func(obj) 

304 except Exception: # pragma: no cover 

305 return obj 

306 if is_method and hasattr(obj, "__get__") and callable(obj.__get__): 

307 obj = obj.__get__(holder) 

308 return obj 

309 

310 

311def getimfunc(func): 

312 try: 

313 return func.__func__ 

314 except AttributeError: 

315 return func 

316 

317 

318def safe_getattr(object: Any, name: str, default: Any) -> Any: 

319 """Like getattr but return default upon any Exception or any OutcomeException. 

320 

321 Attribute access can potentially fail for 'evil' Python objects. 

322 See issue #214. 

323 It catches OutcomeException because of #2490 (issue #580), new outcomes 

324 are derived from BaseException instead of Exception (for more details 

325 check #2707). 

326 """ 

327 from _pytest.outcomes import TEST_OUTCOME 

328 

329 try: 

330 return getattr(object, name, default) 

331 except TEST_OUTCOME: 

332 return default 

333 

334 

335def safe_isclass(obj: object) -> bool: 

336 """Ignore any exception via isinstance on Python 3.""" 

337 try: 

338 return inspect.isclass(obj) 

339 except Exception: 

340 return False 

341 

342 

343if TYPE_CHECKING: 

344 if sys.version_info >= (3, 8): 

345 from typing import final as final 

346 else: 

347 from typing_extensions import final as final 

348elif sys.version_info >= (3, 8): 

349 from typing import final as final 

350else: 

351 

352 def final(f): 

353 return f 

354 

355 

356if sys.version_info >= (3, 8): 

357 from functools import cached_property as cached_property 

358else: 

359 from typing import Type 

360 

361 class cached_property(Generic[_S, _T]): 

362 __slots__ = ("func", "__doc__") 

363 

364 def __init__(self, func: Callable[[_S], _T]) -> None: 

365 self.func = func 

366 self.__doc__ = func.__doc__ 

367 

368 @overload 

369 def __get__( 

370 self, instance: None, owner: Optional[Type[_S]] = ... 

371 ) -> "cached_property[_S, _T]": 

372 ... 

373 

374 @overload 

375 def __get__(self, instance: _S, owner: Optional[Type[_S]] = ...) -> _T: 

376 ... 

377 

378 def __get__(self, instance, owner=None): 

379 if instance is None: 

380 return self 

381 value = instance.__dict__[self.func.__name__] = self.func(instance) 

382 return value 

383 

384 

385# Perform exhaustiveness checking. 

386# 

387# Consider this example: 

388# 

389# MyUnion = Union[int, str] 

390# 

391# def handle(x: MyUnion) -> int { 

392# if isinstance(x, int): 

393# return 1 

394# elif isinstance(x, str): 

395# return 2 

396# else: 

397# raise Exception('unreachable') 

398# 

399# Now suppose we add a new variant: 

400# 

401# MyUnion = Union[int, str, bytes] 

402# 

403# After doing this, we must remember ourselves to go and update the handle 

404# function to handle the new variant. 

405# 

406# With `assert_never` we can do better: 

407# 

408# // raise Exception('unreachable') 

409# return assert_never(x) 

410# 

411# Now, if we forget to handle the new variant, the type-checker will emit a 

412# compile-time error, instead of the runtime error we would have gotten 

413# previously. 

414# 

415# This also work for Enums (if you use `is` to compare) and Literals. 

416def assert_never(value: NoReturn) -> NoReturn: 

417 assert False, f"Unhandled value: {value} ({type(value).__name__})"