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
« 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
20import attr
22import py
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
34if TYPE_CHECKING:
35 from typing_extensions import Final
38_T = TypeVar("_T")
39_S = TypeVar("_S")
41#: constant to prepare valuing pylib path replacements/lazy proxies later on
42# intended for removal in pytest 8.0 or 9.0
44# fmt: off
45# intentional space to create a fake difference for the verification
46LEGACY_PATH = py.path. local
47# fmt: on
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)
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
63if sys.version_info >= (3, 8):
64 import importlib.metadata
66 importlib_metadata = importlib.metadata
67else:
68 import importlib_metadata as importlib_metadata # noqa: F401
71def _format_args(func: Callable[..., Any]) -> str:
72 return str(signature(func))
75def is_generator(func: object) -> bool:
76 genfunc = inspect.isgeneratorfunction(func)
77 return genfunc and not iscoroutinefunction(func)
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.
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)
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)
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)
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
118 mock_sentinel = getattr(sys.modules.get("mock"), "DEFAULT", object())
119 ut_mock_sentinel = getattr(sys.modules.get("unittest.mock"), "DEFAULT", object())
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 )
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.
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.
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.
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.
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
166 fail(
167 f"Could not determine arguments of {function!r}: {e}",
168 pytrace=False,
169 )
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__
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
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 )
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)
221def _translate_non_printable(s: str) -> str:
222 return s.translate(_non_printable_ascii_translate_table)
225STRING_TYPES = bytes, str
228def _bytes_to_ascii(val: bytes) -> str:
229 return val.decode("ascii", "backslashreplace")
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:
236 b'\xc3\xb4\xc5\xd6' -> r'\xc3\xb4\xc5\xd6'
238 and escapes unicode objects into a sequence of escaped unicode
239 ids, e.g.:
241 r'4\nV\U00043efa\x0eMXWB\x1e\u3028\u15fd\xcd\U0007d944'
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)
256@attr.s
257class _PytestWrapper:
258 """Dummy wrapper around a function object for internal use only.
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 """
265 obj = attr.ib()
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
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
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
311def getimfunc(func):
312 try:
313 return func.__func__
314 except AttributeError:
315 return func
318def safe_getattr(object: Any, name: str, default: Any) -> Any:
319 """Like getattr but return default upon any Exception or any OutcomeException.
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
329 try:
330 return getattr(object, name, default)
331 except TEST_OUTCOME:
332 return default
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
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:
352 def final(f):
353 return f
356if sys.version_info >= (3, 8):
357 from functools import cached_property as cached_property
358else:
359 from typing import Type
361 class cached_property(Generic[_S, _T]):
362 __slots__ = ("func", "__doc__")
364 def __init__(self, func: Callable[[_S], _T]) -> None:
365 self.func = func
366 self.__doc__ = func.__doc__
368 @overload
369 def __get__(
370 self, instance: None, owner: Optional[Type[_S]] = ...
371 ) -> "cached_property[_S, _T]":
372 ...
374 @overload
375 def __get__(self, instance: _S, owner: Optional[Type[_S]] = ...) -> _T:
376 ...
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
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__})"