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" ,
@@ -986,7 +1008,40 @@ def __call__(self, function):
986
1008
return function
987
1009
988
1010
989
- def fixture (scope = "function" , params = None , autouse = False , ids = None , name = None ):
1011
+ FIXTURE_ARGS_ORDER = ("scope" , "params" , "autouse" , "ids" , "name" )
1012
+
1013
+
1014
+ def _parse_fixture_args (callable_or_scope , * args , ** kwargs ):
1015
+ arguments = dict (scope = "function" , params = None , autouse = False , ids = None , name = None )
1016
+
1017
+ fixture_function = None
1018
+ if isinstance (callable_or_scope , str ):
1019
+ args = list (args )
1020
+ args .insert (0 , callable_or_scope )
1021
+ else :
1022
+ fixture_function = callable_or_scope
1023
+
1024
+ positionals = set ()
1025
+ for positional , argument_name in zip (args , FIXTURE_ARGS_ORDER ):
1026
+ arguments [argument_name ] = positional
1027
+ positionals .add (argument_name )
1028
+
1029
+ duplicated_kwargs = {kwarg for kwarg in kwargs .keys () if kwarg in positionals }
1030
+ if duplicated_kwargs :
1031
+ raise TypeError (
1032
+ "The fixture arguments are defined as positional and keyword: {}. "
1033
+ "Use only keyword arguments." .format (", " .join (duplicated_kwargs ))
1034
+ )
1035
+
1036
+ if positionals :
1037
+ warnings .warn (FIXTURE_POSITIONAL_ARGUMENTS , stacklevel = 2 )
1038
+
1039
+ arguments .update (kwargs )
1040
+
1041
+ return fixture_function , arguments
1042
+
1043
+
1044
+ def fixture (callable_or_scope = None , * args , ** kwargs ):
990
1045
"""Decorator to mark a fixture factory function.
991
1046
992
1047
This decorator can be used, with or without parameters, to define a
@@ -1032,21 +1087,33 @@ def fixture(scope="function", params=None, autouse=False, ids=None, name=None):
1032
1087
``fixture_<fixturename>`` and then use
1033
1088
``@pytest.fixture(name='<fixturename>')``.
1034
1089
"""
1035
- if callable (scope ) and params is None and autouse is False :
1090
+ fixture_function , arguments = _parse_fixture_args (
1091
+ callable_or_scope , * args , ** kwargs
1092
+ )
1093
+ scope = arguments .get ("scope" )
1094
+ params = arguments .get ("params" )
1095
+ autouse = arguments .get ("autouse" )
1096
+ ids = arguments .get ("ids" )
1097
+ name = arguments .get ("name" )
1098
+
1099
+ if fixture_function and params is None and autouse is False :
1036
1100
# direct decoration
1037
- return FixtureFunctionMarker ("function" , params , autouse , name = name )(scope )
1101
+ return FixtureFunctionMarker (scope , params , autouse , name = name )(
1102
+ fixture_function
1103
+ )
1104
+
1038
1105
if params is not None and not isinstance (params , (list , tuple )):
1039
1106
params = list (params )
1040
1107
return FixtureFunctionMarker (scope , params , autouse , ids = ids , name = name )
1041
1108
1042
1109
1043
- def yield_fixture (scope = "function" , params = None , autouse = False , ids = None , name = None ):
1110
+ def yield_fixture (callable_or_scope = None , * args , ** kwargs ):
1044
1111
""" (return a) decorator to mark a yield-fixture factory function.
1045
1112
1046
1113
.. deprecated:: 3.0
1047
1114
Use :py:func:`pytest.fixture` directly instead.
1048
1115
"""
1049
- return fixture (scope = scope , params = params , autouse = autouse , ids = ids , name = name )
1116
+ return fixture (callable_or_scope = callable_or_scope , * args , ** kwargs )
1050
1117
1051
1118
1052
1119
defaultfuncargprefixmarker = fixture ()
0 commit comments