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

2 import enum

3 import functools

4 import inspect

5 import os

6 import sys

7 from inspect import Parameter

8 from inspect import signature

9 from pathlib import Path

10 from typing import Any

11 from typing import Callable

12 from typing import Generic

13 from typing import NoReturn

14 from typing import Optional

15 from typing import Tuple

16 from typing import TYPE_CHECKING

17 from typing import TypeVar

18 from typing import Union

19

20 import attr

21

22 import 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

31 if True:

32 from typing import overload as overload

33

34 if 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

46 LEGACY_PATH = py.path. local

47# fmt: on

48

49

50 def 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

58 class NotSetType(enum.Enum):

59 token = 0

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

61# fmt: on

62

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

64 import importlib.metadata

65

66 importlib_metadata = importlib.metadata

67 else:

68 import importlib_metadata as importlib_metadata # noqa: F401

69

70

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

72 return str(signature(func))

73

74

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

76 genfunc = inspect.isgeneratorfunction(func)

77 return genfunc and not iscoroutinefunction(func)

78

79

80 def 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

92 def 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

98 def 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

112 def 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

131 def 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

201 def 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

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

222 return s.translate(_non_printable_ascii_translate_table)

223

224

225 STRING_TYPES = bytes, str

226

227

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

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

230

231

232 def 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

257 class _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

268 def 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

297 def 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

311 def getimfunc(func):

312 try:

313 return func.__func__

314 except AttributeError:

315 return func

316

317

318 def 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

335 def 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

343 if 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

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

349 from typing import final as final

350 else:

351

352 def final(f):

353 return f

354

355

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

357 from functools import cached_property as cached_property

358 else:

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.

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

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