2
2
import inspect
3
3
import itertools
4
4
import sys
5
+ import warnings
5
6
from collections import defaultdict
6
7
from collections import deque
7
8
from collections import OrderedDict
27
28
from _pytest .compat import is_generator
28
29
from _pytest .compat import NOTSET
29
30
from _pytest .compat import safe_getattr
31
+ from _pytest .deprecated import FIXTURE_POSITIONAL_ARGUMENTS
30
32
from _pytest .outcomes import fail
31
33
from _pytest .outcomes import TEST_OUTCOME
32
34
@@ -58,7 +60,6 @@ def pytest_sessionstart(session):
58
60
59
61
scopename2class = {} # type: Dict[str, Type[nodes.Node]]
60
62
61
-
62
63
scope2props = dict (session = ()) # type: Dict[str, Tuple[str, ...]]
63
64
scope2props ["package" ] = ("fspath" ,)
64
65
scope2props ["module" ] = ("fspath" , "module" )
@@ -792,6 +793,25 @@ def _teardown_yield_fixture(fixturefunc, it):
792
793
)
793
794
794
795
796
+ def _eval_scope_callable (scope_callable , fixture_name , config ):
797
+ try :
798
+ result = scope_callable (fixture_name = fixture_name , config = config )
799
+ except Exception :
800
+ raise TypeError (
801
+ "Error evaluating {} while defining fixture '{}'.\n "
802
+ "Expected a function with the signature (*, fixture_name, config)" .format (
803
+ scope_callable , fixture_name
804
+ )
805
+ )
806
+ if not isinstance (result , str ):
807
+ fail (
808
+ "Expected {} to return a 'str' while defining fixture '{}', but it returned:\n "
809
+ "{!r}" .format (scope_callable , fixture_name , result ),
810
+ pytrace = False ,
811
+ )
812
+ return result
813
+
814
+
795
815
class FixtureDef :
796
816
""" A container for a factory definition. """
797
817
@@ -811,6 +831,8 @@ def __init__(
811
831
self .has_location = baseid is not None
812
832
self .func = func
813
833
self .argname = argname
834
+ if callable (scope ):
835
+ scope = _eval_scope_callable (scope , argname , fixturemanager .config )
814
836
self .scope = scope
815
837
self .scopenum = scope2index (
816
838
scope or "function" ,
@@ -995,7 +1017,57 @@ def __call__(self, function):
995
1017
return function
996
1018
997
1019
998
- def fixture (scope = "function" , params = None , autouse = False , ids = None , name = None ):
1020
+ FIXTURE_ARGS_ORDER = ("scope" , "params" , "autouse" , "ids" , "name" )
1021
+
1022
+
1023
+ def _parse_fixture_args (callable_or_scope , * args , ** kwargs ):
1024
+ arguments = {
1025
+ "scope" : "function" ,
1026
+ "params" : None ,
1027
+ "autouse" : False ,
1028
+ "ids" : None ,
1029
+ "name" : None ,
1030
+ }
1031
+ kwargs = {
1032
+ key : value for key , value in kwargs .items () if arguments .get (key ) != value
1033
+ }
1034
+
1035
+ fixture_function = None
1036
+ if isinstance (callable_or_scope , str ):
1037
+ args = list (args )
1038
+ args .insert (0 , callable_or_scope )
1039
+ else :
1040
+ fixture_function = callable_or_scope
1041
+
1042
+ positionals = set ()
1043
+ for positional , argument_name in zip (args , FIXTURE_ARGS_ORDER ):
1044
+ arguments [argument_name ] = positional
1045
+ positionals .add (argument_name )
1046
+
1047
+ duplicated_kwargs = {kwarg for kwarg in kwargs .keys () if kwarg in positionals }
1048
+ if duplicated_kwargs :
1049
+ raise TypeError (
1050
+ "The fixture arguments are defined as positional and keyword: {}. "
1051
+ "Use only keyword arguments." .format (", " .join (duplicated_kwargs ))
1052
+ )
1053
+
1054
+ if positionals :
1055
+ warnings .warn (FIXTURE_POSITIONAL_ARGUMENTS , stacklevel = 2 )
1056
+
1057
+ arguments .update (kwargs )
1058
+
1059
+ return fixture_function , arguments
1060
+
1061
+
1062
+ def fixture (
1063
+ callable_or_scope = None ,
1064
+ * args ,
1065
+ scope = "function" ,
1066
+ params = None ,
1067
+ autouse = False ,
1068
+ ids = None ,
1069
+ name = None
1070
+ ):
999
1071
"""Decorator to mark a fixture factory function.
1000
1072
1001
1073
This decorator can be used, with or without parameters, to define a
@@ -1041,21 +1113,55 @@ def fixture(scope="function", params=None, autouse=False, ids=None, name=None):
1041
1113
``fixture_<fixturename>`` and then use
1042
1114
``@pytest.fixture(name='<fixturename>')``.
1043
1115
"""
1044
- if callable (scope ) and params is None and autouse is False :
1116
+ fixture_function , arguments = _parse_fixture_args (
1117
+ callable_or_scope ,
1118
+ * args ,
1119
+ scope = scope ,
1120
+ params = params ,
1121
+ autouse = autouse ,
1122
+ ids = ids ,
1123
+ name = name
1124
+ )
1125
+ scope = arguments .get ("scope" )
1126
+ params = arguments .get ("params" )
1127
+ autouse = arguments .get ("autouse" )
1128
+ ids = arguments .get ("ids" )
1129
+ name = arguments .get ("name" )
1130
+
1131
+ if fixture_function and params is None and autouse is False :
1045
1132
# direct decoration
1046
- return FixtureFunctionMarker ("function" , params , autouse , name = name )(scope )
1133
+ return FixtureFunctionMarker (scope , params , autouse , name = name )(
1134
+ fixture_function
1135
+ )
1136
+
1047
1137
if params is not None and not isinstance (params , (list , tuple )):
1048
1138
params = list (params )
1049
1139
return FixtureFunctionMarker (scope , params , autouse , ids = ids , name = name )
1050
1140
1051
1141
1052
- def yield_fixture (scope = "function" , params = None , autouse = False , ids = None , name = None ):
1142
+ def yield_fixture (
1143
+ callable_or_scope = None ,
1144
+ * args ,
1145
+ scope = "function" ,
1146
+ params = None ,
1147
+ autouse = False ,
1148
+ ids = None ,
1149
+ name = None
1150
+ ):
1053
1151
""" (return a) decorator to mark a yield-fixture factory function.
1054
1152
1055
1153
.. deprecated:: 3.0
1056
1154
Use :py:func:`pytest.fixture` directly instead.
1057
1155
"""
1058
- return fixture (scope = scope , params = params , autouse = autouse , ids = ids , name = name )
1156
+ return fixture (
1157
+ callable_or_scope ,
1158
+ * args ,
1159
+ scope = scope ,
1160
+ params = params ,
1161
+ autouse = autouse ,
1162
+ ids = ids ,
1163
+ name = name
1164
+ )
1059
1165
1060
1166
1061
1167
defaultfuncargprefixmarker = fixture ()
0 commit comments