compat.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435
  1. """Python version compatibility code."""
  2. from __future__ import annotations
  3. import dataclasses
  4. import enum
  5. import functools
  6. import inspect
  7. import os
  8. import sys
  9. from inspect import Parameter
  10. from inspect import signature
  11. from pathlib import Path
  12. from typing import Any
  13. from typing import Callable
  14. from typing import Generic
  15. from typing import NoReturn
  16. from typing import TYPE_CHECKING
  17. from typing import TypeVar
  18. import _pytest._py.path as py_path
  19. # fmt: off
  20. # Workaround for https://github.com/sphinx-doc/sphinx/issues/10351.
  21. # If `overload` is imported from `compat` instead of from `typing`,
  22. # Sphinx doesn't recognize it as `overload` and the API docs for
  23. # overloaded functions look good again. But type checkers handle
  24. # it fine.
  25. # fmt: on
  26. if True:
  27. from typing import overload as overload
  28. if TYPE_CHECKING:
  29. from typing_extensions import Final
  30. _T = TypeVar("_T")
  31. _S = TypeVar("_S")
  32. #: constant to prepare valuing pylib path replacements/lazy proxies later on
  33. # intended for removal in pytest 8.0 or 9.0
  34. # fmt: off
  35. # intentional space to create a fake difference for the verification
  36. LEGACY_PATH = py_path. local
  37. # fmt: on
  38. def legacy_path(path: str | os.PathLike[str]) -> LEGACY_PATH:
  39. """Internal wrapper to prepare lazy proxies for legacy_path instances"""
  40. return LEGACY_PATH(path)
  41. # fmt: off
  42. # Singleton type for NOTSET, as described in:
  43. # https://www.python.org/dev/peps/pep-0484/#support-for-singleton-types-in-unions
  44. class NotSetType(enum.Enum):
  45. token = 0
  46. NOTSET: Final = NotSetType.token # noqa: E305
  47. # fmt: on
  48. if sys.version_info >= (3, 8):
  49. import importlib.metadata
  50. importlib_metadata = importlib.metadata
  51. else:
  52. import importlib_metadata as importlib_metadata # noqa: F401
  53. def _format_args(func: Callable[..., Any]) -> str:
  54. return str(signature(func))
  55. def is_generator(func: object) -> bool:
  56. genfunc = inspect.isgeneratorfunction(func)
  57. return genfunc and not iscoroutinefunction(func)
  58. def iscoroutinefunction(func: object) -> bool:
  59. """Return True if func is a coroutine function (a function defined with async
  60. def syntax, and doesn't contain yield), or a function decorated with
  61. @asyncio.coroutine.
  62. Note: copied and modified from Python 3.5's builtin couroutines.py to avoid
  63. importing asyncio directly, which in turns also initializes the "logging"
  64. module as a side-effect (see issue #8).
  65. """
  66. return inspect.iscoroutinefunction(func) or getattr(func, "_is_coroutine", False)
  67. def is_async_function(func: object) -> bool:
  68. """Return True if the given function seems to be an async function or
  69. an async generator."""
  70. return iscoroutinefunction(func) or inspect.isasyncgenfunction(func)
  71. def getlocation(function, curdir: str | None = None) -> str:
  72. function = get_real_func(function)
  73. fn = Path(inspect.getfile(function))
  74. lineno = function.__code__.co_firstlineno
  75. if curdir is not None:
  76. try:
  77. relfn = fn.relative_to(curdir)
  78. except ValueError:
  79. pass
  80. else:
  81. return "%s:%d" % (relfn, lineno + 1)
  82. return "%s:%d" % (fn, lineno + 1)
  83. def num_mock_patch_args(function) -> int:
  84. """Return number of arguments used up by mock arguments (if any)."""
  85. patchings = getattr(function, "patchings", None)
  86. if not patchings:
  87. return 0
  88. mock_sentinel = getattr(sys.modules.get("mock"), "DEFAULT", object())
  89. ut_mock_sentinel = getattr(sys.modules.get("unittest.mock"), "DEFAULT", object())
  90. return len(
  91. [
  92. p
  93. for p in patchings
  94. if not p.attribute_name
  95. and (p.new is mock_sentinel or p.new is ut_mock_sentinel)
  96. ]
  97. )
  98. def getfuncargnames(
  99. function: Callable[..., Any],
  100. *,
  101. name: str = "",
  102. is_method: bool = False,
  103. cls: type | None = None,
  104. ) -> tuple[str, ...]:
  105. """Return the names of a function's mandatory arguments.
  106. Should return the names of all function arguments that:
  107. * Aren't bound to an instance or type as in instance or class methods.
  108. * Don't have default values.
  109. * Aren't bound with functools.partial.
  110. * Aren't replaced with mocks.
  111. The is_method and cls arguments indicate that the function should
  112. be treated as a bound method even though it's not unless, only in
  113. the case of cls, the function is a static method.
  114. The name parameter should be the original name in which the function was collected.
  115. """
  116. # TODO(RonnyPfannschmidt): This function should be refactored when we
  117. # revisit fixtures. The fixture mechanism should ask the node for
  118. # the fixture names, and not try to obtain directly from the
  119. # function object well after collection has occurred.
  120. # The parameters attribute of a Signature object contains an
  121. # ordered mapping of parameter names to Parameter instances. This
  122. # creates a tuple of the names of the parameters that don't have
  123. # defaults.
  124. try:
  125. parameters = signature(function).parameters
  126. except (ValueError, TypeError) as e:
  127. from _pytest.outcomes import fail
  128. fail(
  129. f"Could not determine arguments of {function!r}: {e}",
  130. pytrace=False,
  131. )
  132. arg_names = tuple(
  133. p.name
  134. for p in parameters.values()
  135. if (
  136. p.kind is Parameter.POSITIONAL_OR_KEYWORD
  137. or p.kind is Parameter.KEYWORD_ONLY
  138. )
  139. and p.default is Parameter.empty
  140. )
  141. if not name:
  142. name = function.__name__
  143. # If this function should be treated as a bound method even though
  144. # it's passed as an unbound method or function, remove the first
  145. # parameter name.
  146. if is_method or (
  147. # Not using `getattr` because we don't want to resolve the staticmethod.
  148. # Not using `cls.__dict__` because we want to check the entire MRO.
  149. cls
  150. and not isinstance(
  151. inspect.getattr_static(cls, name, default=None), staticmethod
  152. )
  153. ):
  154. arg_names = arg_names[1:]
  155. # Remove any names that will be replaced with mocks.
  156. if hasattr(function, "__wrapped__"):
  157. arg_names = arg_names[num_mock_patch_args(function) :]
  158. return arg_names
  159. def get_default_arg_names(function: Callable[..., Any]) -> tuple[str, ...]:
  160. # Note: this code intentionally mirrors the code at the beginning of
  161. # getfuncargnames, to get the arguments which were excluded from its result
  162. # because they had default values.
  163. return tuple(
  164. p.name
  165. for p in signature(function).parameters.values()
  166. if p.kind in (Parameter.POSITIONAL_OR_KEYWORD, Parameter.KEYWORD_ONLY)
  167. and p.default is not Parameter.empty
  168. )
  169. _non_printable_ascii_translate_table = {
  170. i: f"\\x{i:02x}" for i in range(128) if i not in range(32, 127)
  171. }
  172. _non_printable_ascii_translate_table.update(
  173. {ord("\t"): "\\t", ord("\r"): "\\r", ord("\n"): "\\n"}
  174. )
  175. def _translate_non_printable(s: str) -> str:
  176. return s.translate(_non_printable_ascii_translate_table)
  177. STRING_TYPES = bytes, str
  178. def _bytes_to_ascii(val: bytes) -> str:
  179. return val.decode("ascii", "backslashreplace")
  180. def ascii_escaped(val: bytes | str) -> str:
  181. r"""If val is pure ASCII, return it as an str, otherwise, escape
  182. bytes objects into a sequence of escaped bytes:
  183. b'\xc3\xb4\xc5\xd6' -> r'\xc3\xb4\xc5\xd6'
  184. and escapes unicode objects into a sequence of escaped unicode
  185. ids, e.g.:
  186. r'4\nV\U00043efa\x0eMXWB\x1e\u3028\u15fd\xcd\U0007d944'
  187. Note:
  188. The obvious "v.decode('unicode-escape')" will return
  189. valid UTF-8 unicode if it finds them in bytes, but we
  190. want to return escaped bytes for any byte, even if they match
  191. a UTF-8 string.
  192. """
  193. if isinstance(val, bytes):
  194. ret = _bytes_to_ascii(val)
  195. else:
  196. ret = val
  197. return ret
  198. @dataclasses.dataclass
  199. class _PytestWrapper:
  200. """Dummy wrapper around a function object for internal use only.
  201. Used to correctly unwrap the underlying function object when we are
  202. creating fixtures, because we wrap the function object ourselves with a
  203. decorator to issue warnings when the fixture function is called directly.
  204. """
  205. obj: Any
  206. def get_real_func(obj):
  207. """Get the real function object of the (possibly) wrapped object by
  208. functools.wraps or functools.partial."""
  209. start_obj = obj
  210. for i in range(100):
  211. # __pytest_wrapped__ is set by @pytest.fixture when wrapping the fixture function
  212. # to trigger a warning if it gets called directly instead of by pytest: we don't
  213. # want to unwrap further than this otherwise we lose useful wrappings like @mock.patch (#3774)
  214. new_obj = getattr(obj, "__pytest_wrapped__", None)
  215. if isinstance(new_obj, _PytestWrapper):
  216. obj = new_obj.obj
  217. break
  218. new_obj = getattr(obj, "__wrapped__", None)
  219. if new_obj is None:
  220. break
  221. obj = new_obj
  222. else:
  223. from _pytest._io.saferepr import saferepr
  224. raise ValueError(
  225. ("could not find real function of {start}\nstopped at {current}").format(
  226. start=saferepr(start_obj), current=saferepr(obj)
  227. )
  228. )
  229. if isinstance(obj, functools.partial):
  230. obj = obj.func
  231. return obj
  232. def get_real_method(obj, holder):
  233. """Attempt to obtain the real function object that might be wrapping
  234. ``obj``, while at the same time returning a bound method to ``holder`` if
  235. the original object was a bound method."""
  236. try:
  237. is_method = hasattr(obj, "__func__")
  238. obj = get_real_func(obj)
  239. except Exception: # pragma: no cover
  240. return obj
  241. if is_method and hasattr(obj, "__get__") and callable(obj.__get__):
  242. obj = obj.__get__(holder)
  243. return obj
  244. def getimfunc(func):
  245. try:
  246. return func.__func__
  247. except AttributeError:
  248. return func
  249. def safe_getattr(object: Any, name: str, default: Any) -> Any:
  250. """Like getattr but return default upon any Exception or any OutcomeException.
  251. Attribute access can potentially fail for 'evil' Python objects.
  252. See issue #214.
  253. It catches OutcomeException because of #2490 (issue #580), new outcomes
  254. are derived from BaseException instead of Exception (for more details
  255. check #2707).
  256. """
  257. from _pytest.outcomes import TEST_OUTCOME
  258. try:
  259. return getattr(object, name, default)
  260. except TEST_OUTCOME:
  261. return default
  262. def safe_isclass(obj: object) -> bool:
  263. """Ignore any exception via isinstance on Python 3."""
  264. try:
  265. return inspect.isclass(obj)
  266. except Exception:
  267. return False
  268. if TYPE_CHECKING:
  269. if sys.version_info >= (3, 8):
  270. from typing import final as final
  271. else:
  272. from typing_extensions import final as final
  273. elif sys.version_info >= (3, 8):
  274. from typing import final as final
  275. else:
  276. def final(f):
  277. return f
  278. if sys.version_info >= (3, 8):
  279. from functools import cached_property as cached_property
  280. else:
  281. class cached_property(Generic[_S, _T]):
  282. __slots__ = ("func", "__doc__")
  283. def __init__(self, func: Callable[[_S], _T]) -> None:
  284. self.func = func
  285. self.__doc__ = func.__doc__
  286. @overload
  287. def __get__(
  288. self, instance: None, owner: type[_S] | None = ...
  289. ) -> cached_property[_S, _T]:
  290. ...
  291. @overload
  292. def __get__(self, instance: _S, owner: type[_S] | None = ...) -> _T:
  293. ...
  294. def __get__(self, instance, owner=None):
  295. if instance is None:
  296. return self
  297. value = instance.__dict__[self.func.__name__] = self.func(instance)
  298. return value
  299. def get_user_id() -> int | None:
  300. """Return the current process's real user id or None if it could not be
  301. determined.
  302. :return: The user id or None if it could not be determined.
  303. """
  304. # mypy follows the version and platform checking expectation of PEP 484:
  305. # https://mypy.readthedocs.io/en/stable/common_issues.html?highlight=platform#python-version-and-system-platform-checks
  306. # Containment checks are too complex for mypy v1.5.0 and cause failure.
  307. if sys.platform == "win32" or sys.platform == "emscripten":
  308. # win32 does not have a getuid() function.
  309. # Emscripten has a return 0 stub.
  310. return None
  311. else:
  312. # On other platforms, a return value of -1 is assumed to indicate that
  313. # the current process's real user id could not be determined.
  314. ERROR = -1
  315. uid = os.getuid()
  316. return uid if uid != ERROR else None
  317. # Perform exhaustiveness checking.
  318. #
  319. # Consider this example:
  320. #
  321. # MyUnion = Union[int, str]
  322. #
  323. # def handle(x: MyUnion) -> int {
  324. # if isinstance(x, int):
  325. # return 1
  326. # elif isinstance(x, str):
  327. # return 2
  328. # else:
  329. # raise Exception('unreachable')
  330. #
  331. # Now suppose we add a new variant:
  332. #
  333. # MyUnion = Union[int, str, bytes]
  334. #
  335. # After doing this, we must remember ourselves to go and update the handle
  336. # function to handle the new variant.
  337. #
  338. # With `assert_never` we can do better:
  339. #
  340. # // raise Exception('unreachable')
  341. # return assert_never(x)
  342. #
  343. # Now, if we forget to handle the new variant, the type-checker will emit a
  344. # compile-time error, instead of the runtime error we would have gotten
  345. # previously.
  346. #
  347. # This also work for Enums (if you use `is` to compare) and Literals.
  348. def assert_never(value: NoReturn) -> NoReturn:
  349. assert False, f"Unhandled value: {value} ({type(value).__name__})"