4040from _pytest ._code .code import FormattedExcinfo
4141from _pytest ._code .code import TerminalRepr
4242from _pytest ._io import TerminalWriter
43- from _pytest .compat import _PytestWrapper
4443from _pytest .compat import assert_never
4544from _pytest .compat import get_real_func
46- from _pytest .compat import get_real_method
4745from _pytest .compat import getfuncargnames
4846from _pytest .compat import getimfunc
4947from _pytest .compat import getlocation
@@ -152,13 +150,12 @@ def get_scope_node(node: nodes.Node, scope: Scope) -> nodes.Node | None:
152150 assert_never (scope )
153151
154152
153+ # TODO: Try to use FixtureFunctionDefinition instead of the marker
155154def getfixturemarker (obj : object ) -> FixtureFunctionMarker | None :
156- """Return fixturemarker or None if it doesn't exist or raised
157- exceptions."""
158- return cast (
159- Optional [FixtureFunctionMarker ],
160- safe_getattr (obj , "_pytestfixturefunction" , None ),
161- )
155+ """Return fixturemarker or None if it doesn't exist"""
156+ if isinstance (obj , FixtureFunctionDefinition ):
157+ return obj ._fixture_function_marker
158+ return None
162159
163160
164161# Algorithm for sorting on a per-parametrized resource setup basis.
@@ -1181,31 +1178,6 @@ def pytest_fixture_setup(
11811178 return result
11821179
11831180
1184- def wrap_function_to_error_out_if_called_directly (
1185- function : FixtureFunction ,
1186- fixture_marker : FixtureFunctionMarker ,
1187- ) -> FixtureFunction :
1188- """Wrap the given fixture function so we can raise an error about it being called directly,
1189- instead of used as an argument in a test function."""
1190- name = fixture_marker .name or function .__name__
1191- message = (
1192- f'Fixture "{ name } " called directly. Fixtures are not meant to be called directly,\n '
1193- "but are created automatically when test functions request them as parameters.\n "
1194- "See https://docs.pytest.org/en/stable/explanation/fixtures.html for more information about fixtures, and\n "
1195- "https://docs.pytest.org/en/stable/deprecations.html#calling-fixtures-directly about how to update your code."
1196- )
1197-
1198- @functools .wraps (function )
1199- def result (* args , ** kwargs ):
1200- fail (message , pytrace = False )
1201-
1202- # Keep reference to the original function in our own custom attribute so we don't unwrap
1203- # further than this point and lose useful wrappings like @mock.patch (#3774).
1204- result .__pytest_wrapped__ = _PytestWrapper (function ) # type: ignore[attr-defined]
1205-
1206- return cast (FixtureFunction , result )
1207-
1208-
12091181@final
12101182@dataclasses .dataclass (frozen = True )
12111183class FixtureFunctionMarker :
@@ -1220,19 +1192,21 @@ class FixtureFunctionMarker:
12201192 def __post_init__ (self , _ispytest : bool ) -> None :
12211193 check_ispytest (_ispytest )
12221194
1223- def __call__ (self , function : FixtureFunction ) -> FixtureFunction :
1195+ def __call__ (self , function : FixtureFunction ) -> FixtureFunctionDefinition :
12241196 if inspect .isclass (function ):
12251197 raise ValueError ("class fixtures not supported (maybe in the future)" )
12261198
1227- if getattr (function , "_pytestfixturefunction" , False ):
1199+ if isinstance (function , FixtureFunctionDefinition ):
12281200 raise ValueError (
12291201 f"@pytest.fixture is being applied more than once to the same function { function .__name__ !r} "
12301202 )
12311203
12321204 if hasattr (function , "pytestmark" ):
12331205 warnings .warn (MARKED_FIXTURE , stacklevel = 2 )
12341206
1235- function = wrap_function_to_error_out_if_called_directly (function , self )
1207+ fixture_definition = FixtureFunctionDefinition (
1208+ function = function , fixture_function_marker = self , _ispytest = True
1209+ )
12361210
12371211 name = self .name or function .__name__
12381212 if name == "request" :
@@ -1242,21 +1216,68 @@ def __call__(self, function: FixtureFunction) -> FixtureFunction:
12421216 pytrace = False ,
12431217 )
12441218
1245- # Type ignored because https://github.com/python/mypy/issues/2087.
1246- function ._pytestfixturefunction = self # type: ignore[attr-defined]
1247- return function
1219+ return fixture_definition
1220+
1221+
1222+ # TODO: paramspec/return type annotation tracking and storing
1223+ class FixtureFunctionDefinition :
1224+ def __init__ (
1225+ self ,
1226+ * ,
1227+ function : Callable [..., Any ],
1228+ fixture_function_marker : FixtureFunctionMarker ,
1229+ instance : object | None = None ,
1230+ _ispytest : bool = False ,
1231+ ) -> None :
1232+ check_ispytest (_ispytest )
1233+ self .name = fixture_function_marker .name or function .__name__
1234+ # In order to show the function that this fixture contains in messages.
1235+ # Set the __name__ to be same as the function __name__ or the given fixture name.
1236+ self .__name__ = self .name
1237+ self ._fixture_function_marker = fixture_function_marker
1238+ if instance is not None :
1239+ self ._fixture_function = cast (
1240+ Callable [..., Any ], function .__get__ (instance )
1241+ )
1242+ else :
1243+ self ._fixture_function = function
1244+ functools .update_wrapper (self , function )
1245+
1246+ def __repr__ (self ) -> str :
1247+ return f"<pytest_fixture({ self ._fixture_function } )>"
1248+
1249+ def __get__ (self , instance , owner = None ):
1250+ """Behave like a method if the function it was applied to was a method."""
1251+ return FixtureFunctionDefinition (
1252+ function = self ._fixture_function ,
1253+ fixture_function_marker = self ._fixture_function_marker ,
1254+ instance = instance ,
1255+ _ispytest = True ,
1256+ )
1257+
1258+ def __call__ (self , * args : Any , ** kwds : Any ) -> Any :
1259+ message = (
1260+ f'Fixture "{ self .name } " called directly. Fixtures are not meant to be called directly,\n '
1261+ "but are created automatically when test functions request them as parameters.\n "
1262+ "See https://docs.pytest.org/en/stable/explanation/fixtures.html for more information about fixtures, and\n "
1263+ "https://docs.pytest.org/en/stable/deprecations.html#calling-fixtures-directly"
1264+ )
1265+ fail (message , pytrace = False )
1266+
1267+ def _get_wrapped_function (self ) -> Callable [..., Any ]:
1268+ return self ._fixture_function
12481269
12491270
12501271@overload
12511272def fixture (
1252- fixture_function : FixtureFunction ,
1273+ fixture_function : Callable [..., object ] ,
12531274 * ,
12541275 scope : _ScopeName | Callable [[str , Config ], _ScopeName ] = ...,
12551276 params : Iterable [object ] | None = ...,
12561277 autouse : bool = ...,
12571278 ids : Sequence [object | None ] | Callable [[Any ], object | None ] | None = ...,
12581279 name : str | None = ...,
1259- ) -> FixtureFunction : ...
1280+ ) -> FixtureFunctionDefinition : ...
12601281
12611282
12621283@overload
@@ -1279,7 +1300,7 @@ def fixture(
12791300 autouse : bool = False ,
12801301 ids : Sequence [object | None ] | Callable [[Any ], object | None ] | None = None ,
12811302 name : str | None = None ,
1282- ) -> FixtureFunctionMarker | FixtureFunction :
1303+ ) -> FixtureFunctionMarker | FixtureFunctionDefinition :
12831304 """Decorator to mark a fixture factory function.
12841305
12851306 This decorator can be used, with or without parameters, to define a
@@ -1774,33 +1795,31 @@ def parsefactories(
17741795 # The attribute can be an arbitrary descriptor, so the attribute
17751796 # access below can raise. safe_getattr() ignores such exceptions.
17761797 obj_ub = safe_getattr (holderobj_tp , name , None )
1777- marker = getfixturemarker (obj_ub )
1778- if not isinstance (marker , FixtureFunctionMarker ):
1779- # Magic globals with __getattr__ might have got us a wrong
1780- # fixture attribute.
1781- continue
1782-
1783- # OK we know it is a fixture -- now safe to look up on the _instance_.
1784- obj = getattr (holderobj , name )
1785-
1786- if marker .name :
1787- name = marker .name
1788-
1789- # During fixture definition we wrap the original fixture function
1790- # to issue a warning if called directly, so here we unwrap it in
1791- # order to not emit the warning when pytest itself calls the
1792- # fixture function.
1793- func = get_real_method (obj , holderobj )
1794-
1795- self ._register_fixture (
1796- name = name ,
1797- nodeid = nodeid ,
1798- func = func ,
1799- scope = marker .scope ,
1800- params = marker .params ,
1801- ids = marker .ids ,
1802- autouse = marker .autouse ,
1803- )
1798+ if type (obj_ub ) is FixtureFunctionDefinition :
1799+ marker = obj_ub ._fixture_function_marker
1800+ if marker .name :
1801+ fixture_name = marker .name
1802+ else :
1803+ fixture_name = name
1804+
1805+ # OK we know it is a fixture -- now safe to look up on the _instance_.
1806+ try :
1807+ obj = getattr (holderobj , name )
1808+ # if the fixture is named in the decorator we cannot find it in the module
1809+ except AttributeError :
1810+ obj = obj_ub
1811+
1812+ func = obj ._get_wrapped_function ()
1813+
1814+ self ._register_fixture (
1815+ name = fixture_name ,
1816+ nodeid = nodeid ,
1817+ func = func ,
1818+ scope = marker .scope ,
1819+ params = marker .params ,
1820+ ids = marker .ids ,
1821+ autouse = marker .autouse ,
1822+ )
18041823
18051824 def getfixturedefs (
18061825 self , argname : str , node : nodes .Node
0 commit comments