refactor: removed PytestWrapper class

This commit is contained in:
Glyphack 2024-06-20 15:16:47 +02:00
parent ef46a374a0
commit 91fc20876a
3 changed files with 15 additions and 31 deletions

View File

@ -3,7 +3,6 @@
from __future__ import annotations from __future__ import annotations
import dataclasses
import enum import enum
import functools import functools
import inspect import inspect
@ -210,30 +209,15 @@ def ascii_escaped(val: bytes | str) -> str:
return ret.translate(_non_printable_ascii_translate_table) return ret.translate(_non_printable_ascii_translate_table)
# TODO: remove and replace with FixtureFunctionDefinition
@dataclasses.dataclass
class _PytestWrapper:
"""Dummy wrapper around a function object for internal use only.
Used to correctly unwrap the underlying function object when we are
creating fixtures, because we wrap the function object ourselves with a
decorator to issue warnings when the fixture function is called directly.
"""
obj: Any
def get_real_func(obj): def get_real_func(obj):
"""Get the real function object of the (possibly) wrapped object by """Get the real function object of the (possibly) wrapped object by
functools.wraps or functools.partial.""" functools.wraps or functools.partial or pytest.fixture"""
from _pytest.fixtures import FixtureFunctionDefinition
start_obj = obj start_obj = obj
for i in range(100): for _ in range(100):
# __pytest_wrapped__ is set by @pytest.fixture when wrapping the fixture function if isinstance(obj, FixtureFunctionDefinition):
# to trigger a warning if it gets called directly instead of by pytest: we don't obj = obj.get_real_func()
# want to unwrap further than this otherwise we lose useful wrappings like @mock.patch (#3774)
new_obj = getattr(obj, "__pytest_wrapped__", None)
if isinstance(new_obj, _PytestWrapper):
obj = new_obj.obj
break break
new_obj = getattr(obj, "__wrapped__", None) new_obj = getattr(obj, "__wrapped__", None)
if new_obj is None: if new_obj is None:

View File

@ -42,7 +42,6 @@ from _pytest._code import Source
from _pytest._code.code import FormattedExcinfo from _pytest._code.code import FormattedExcinfo
from _pytest._code.code import TerminalRepr from _pytest._code.code import TerminalRepr
from _pytest._io import TerminalWriter from _pytest._io import TerminalWriter
from _pytest.compat import _PytestWrapper
from _pytest.compat import assert_never from _pytest.compat import assert_never
from _pytest.compat import get_real_func from _pytest.compat import get_real_func
from _pytest.compat import getfuncargnames from _pytest.compat import getfuncargnames
@ -1204,7 +1203,6 @@ class FixtureFunctionDefinition:
# Using isinstance on every object in code might execute code that is not intended to be executed. # Using isinstance on every object in code might execute code that is not intended to be executed.
# Like lazy loaded classes. # Like lazy loaded classes.
self._pytestfixturefunction = fixture_function_marker self._pytestfixturefunction = fixture_function_marker
self.__pytest_wrapped__ = _PytestWrapper(function)
self.fixture_function_marker = fixture_function_marker self.fixture_function_marker = fixture_function_marker
self.fixture_function = function self.fixture_function = function
self.instance = instance self.instance = instance

View File

@ -7,7 +7,6 @@ import sys
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from typing import Union from typing import Union
from _pytest.compat import _PytestWrapper
from _pytest.compat import assert_never from _pytest.compat import assert_never
from _pytest.compat import get_real_func from _pytest.compat import get_real_func
from _pytest.compat import is_generator from _pytest.compat import is_generator
@ -52,8 +51,8 @@ def test_real_func_loop_limit() -> None:
with pytest.raises( with pytest.raises(
ValueError, ValueError,
match=( match=(
"could not find real function of <Evil left=800>\n" "could not find real function of <Evil left=900>\n"
"stopped at <Evil left=800>" "stopped at <Evil left=900>"
), ),
): ):
get_real_func(evil) get_real_func(evil)
@ -78,10 +77,13 @@ def test_get_real_func() -> None:
wrapped_func2 = decorator(decorator(wrapped_func)) wrapped_func2 = decorator(decorator(wrapped_func))
assert get_real_func(wrapped_func2) is func assert get_real_func(wrapped_func2) is func
# special case for __pytest_wrapped__ attribute: used to obtain the function up until the point # obtain the function up until the point a function was wrapped by pytest itself
# a function was wrapped by pytest itself @pytest.fixture
wrapped_func2.__pytest_wrapped__ = _PytestWrapper(wrapped_func) def wrapped_func3():
assert get_real_func(wrapped_func2) is wrapped_func pass
wrapped_func4 = decorator(wrapped_func3)
assert get_real_func(wrapped_func4) is wrapped_func3.get_real_func()
def test_get_real_func_partial() -> None: def test_get_real_func_partial() -> None: