40
40
from _pytest ._code .code import FormattedExcinfo
41
41
from _pytest ._code .code import TerminalRepr
42
42
from _pytest ._io import TerminalWriter
43
- from _pytest .compat import _PytestWrapper
44
43
from _pytest .compat import assert_never
45
44
from _pytest .compat import get_real_func
46
- from _pytest .compat import get_real_method
47
45
from _pytest .compat import getfuncargnames
48
46
from _pytest .compat import getimfunc
49
47
from _pytest .compat import getlocation
@@ -152,13 +150,12 @@ def get_scope_node(node: nodes.Node, scope: Scope) -> nodes.Node | None:
152
150
assert_never (scope )
153
151
154
152
153
+ # TODO: Try to use FixtureFunctionDefinition instead of the marker
155
154
def 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
162
159
163
160
164
161
# Algorithm for sorting on a per-parametrized resource setup basis.
@@ -1181,31 +1178,6 @@ def pytest_fixture_setup(
1181
1178
return result
1182
1179
1183
1180
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
-
1209
1181
@final
1210
1182
@dataclasses .dataclass (frozen = True )
1211
1183
class FixtureFunctionMarker :
@@ -1220,19 +1192,21 @@ class FixtureFunctionMarker:
1220
1192
def __post_init__ (self , _ispytest : bool ) -> None :
1221
1193
check_ispytest (_ispytest )
1222
1194
1223
- def __call__ (self , function : FixtureFunction ) -> FixtureFunction :
1195
+ def __call__ (self , function : FixtureFunction ) -> FixtureFunctionDefinition :
1224
1196
if inspect .isclass (function ):
1225
1197
raise ValueError ("class fixtures not supported (maybe in the future)" )
1226
1198
1227
- if getattr (function , "_pytestfixturefunction" , False ):
1199
+ if isinstance (function , FixtureFunctionDefinition ):
1228
1200
raise ValueError (
1229
1201
f"@pytest.fixture is being applied more than once to the same function { function .__name__ !r} "
1230
1202
)
1231
1203
1232
1204
if hasattr (function , "pytestmark" ):
1233
1205
warnings .warn (MARKED_FIXTURE , stacklevel = 2 )
1234
1206
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
+ )
1236
1210
1237
1211
name = self .name or function .__name__
1238
1212
if name == "request" :
@@ -1242,21 +1216,68 @@ def __call__(self, function: FixtureFunction) -> FixtureFunction:
1242
1216
pytrace = False ,
1243
1217
)
1244
1218
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
1248
1269
1249
1270
1250
1271
@overload
1251
1272
def fixture (
1252
- fixture_function : FixtureFunction ,
1273
+ fixture_function : Callable [..., object ] ,
1253
1274
* ,
1254
1275
scope : _ScopeName | Callable [[str , Config ], _ScopeName ] = ...,
1255
1276
params : Iterable [object ] | None = ...,
1256
1277
autouse : bool = ...,
1257
1278
ids : Sequence [object | None ] | Callable [[Any ], object | None ] | None = ...,
1258
1279
name : str | None = ...,
1259
- ) -> FixtureFunction : ...
1280
+ ) -> FixtureFunctionDefinition : ...
1260
1281
1261
1282
1262
1283
@overload
@@ -1279,7 +1300,7 @@ def fixture(
1279
1300
autouse : bool = False ,
1280
1301
ids : Sequence [object | None ] | Callable [[Any ], object | None ] | None = None ,
1281
1302
name : str | None = None ,
1282
- ) -> FixtureFunctionMarker | FixtureFunction :
1303
+ ) -> FixtureFunctionMarker | FixtureFunctionDefinition :
1283
1304
"""Decorator to mark a fixture factory function.
1284
1305
1285
1306
This decorator can be used, with or without parameters, to define a
@@ -1774,33 +1795,31 @@ def parsefactories(
1774
1795
# The attribute can be an arbitrary descriptor, so the attribute
1775
1796
# access below can raise. safe_getattr() ignores such exceptions.
1776
1797
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
+ )
1804
1823
1805
1824
def getfixturedefs (
1806
1825
self , argname : str , node : nodes .Node
0 commit comments