1010from typing import Callable
1111from typing import cast
1212from typing import Dict
13+ from typing import Generator
14+ from typing import Generic
1315from typing import Iterable
1416from typing import Iterator
1517from typing import List
1618from typing import Optional
1719from typing import Sequence
1820from typing import Set
1921from typing import Tuple
22+ from typing import TypeVar
2023from typing import Union
2124
2225import attr
3740from _pytest .compat import is_generator
3841from _pytest .compat import NOTSET
3942from _pytest .compat import order_preserving_dict
43+ from _pytest .compat import overload
4044from _pytest .compat import safe_getattr
4145from _pytest .compat import TYPE_CHECKING
4246from _pytest .config import _PluggyPlugin
6468 _Scope = Literal ["session" , "package" , "module" , "class" , "function" ]
6569
6670
67- _FixtureCachedResult = Tuple [
68- # The result.
69- Optional [object ],
70- # Cache key.
71- object ,
72- # Exc info if raised.
73- Optional [Tuple ["Type[BaseException]" , BaseException , TracebackType ]],
71+ # The value of the fixture -- return/yield of the fixture function (type variable).
72+ _FixtureValue = TypeVar ("_FixtureValue" )
73+ # The type of the fixture function (type variable).
74+ _FixtureFunction = TypeVar ("_FixtureFunction" , bound = Callable [..., object ])
75+ # The type of a fixture function (type alias generic in fixture value).
76+ _FixtureFunc = Union [
77+ Callable [..., _FixtureValue ], Callable [..., Generator [_FixtureValue , None , None ]]
78+ ]
79+ # The type of FixtureDef.cached_result (type alias generic in fixture value).
80+ _FixtureCachedResult = Union [
81+ Tuple [
82+ # The result.
83+ _FixtureValue ,
84+ # Cache key.
85+ object ,
86+ None ,
87+ ],
88+ Tuple [
89+ None ,
90+ # Cache key.
91+ object ,
92+ # Exc info if raised.
93+ Tuple ["Type[BaseException]" , BaseException , TracebackType ],
94+ ],
7495]
7596
7697
@@ -871,9 +892,13 @@ def fail_fixturefunc(fixturefunc, msg: str) -> "NoReturn":
871892 fail (msg + ":\n \n " + str (source .indent ()) + "\n " + location , pytrace = False )
872893
873894
874- def call_fixture_func (fixturefunc , request : FixtureRequest , kwargs ):
875- yieldctx = is_generator (fixturefunc )
876- if yieldctx :
895+ def call_fixture_func (
896+ fixturefunc : "_FixtureFunc[_FixtureValue]" , request : FixtureRequest , kwargs
897+ ) -> _FixtureValue :
898+ if is_generator (fixturefunc ):
899+ fixturefunc = cast (
900+ Callable [..., Generator [_FixtureValue , None , None ]], fixturefunc
901+ )
877902 generator = fixturefunc (** kwargs )
878903 try :
879904 fixture_result = next (generator )
@@ -884,6 +909,7 @@ def call_fixture_func(fixturefunc, request: FixtureRequest, kwargs):
884909 finalizer = functools .partial (_teardown_yield_fixture , fixturefunc , generator )
885910 request .addfinalizer (finalizer )
886911 else :
912+ fixturefunc = cast (Callable [..., _FixtureValue ], fixturefunc )
887913 fixture_result = fixturefunc (** kwargs )
888914 return fixture_result
889915
@@ -926,15 +952,15 @@ def _eval_scope_callable(
926952 return result
927953
928954
929- class FixtureDef :
955+ class FixtureDef ( Generic [ _FixtureValue ]) :
930956 """ A container for a factory definition. """
931957
932958 def __init__ (
933959 self ,
934960 fixturemanager : "FixtureManager" ,
935961 baseid ,
936962 argname : str ,
937- func ,
963+ func : "_FixtureFunc[_FixtureValue]" ,
938964 scope : "Union[_Scope, Callable[[str, Config], _Scope]]" ,
939965 params : Optional [Sequence [object ]],
940966 unittest : bool = False ,
@@ -966,7 +992,7 @@ def __init__(
966992 ) # type: Tuple[str, ...]
967993 self .unittest = unittest
968994 self .ids = ids
969- self .cached_result = None # type: Optional[_FixtureCachedResult]
995+ self .cached_result = None # type: Optional[_FixtureCachedResult[_FixtureValue] ]
970996 self ._finalizers = [] # type: List[Callable[[], object]]
971997
972998 def addfinalizer (self , finalizer : Callable [[], object ]) -> None :
@@ -996,7 +1022,7 @@ def finish(self, request: SubRequest) -> None:
9961022 self .cached_result = None
9971023 self ._finalizers = []
9981024
999- def execute (self , request : SubRequest ):
1025+ def execute (self , request : SubRequest ) -> _FixtureValue :
10001026 # get required arguments and register our own finish()
10011027 # with their finalization
10021028 for argname in self .argnames :
@@ -1008,22 +1034,24 @@ def execute(self, request: SubRequest):
10081034
10091035 my_cache_key = self .cache_key (request )
10101036 if self .cached_result is not None :
1011- result , cache_key , err = self .cached_result
10121037 # note: comparison with `==` can fail (or be expensive) for e.g.
10131038 # numpy arrays (#6497)
1039+ cache_key = self .cached_result [1 ]
10141040 if my_cache_key is cache_key :
1015- if err is not None :
1016- _ , val , tb = err
1041+ if self . cached_result [ 2 ] is not None :
1042+ _ , val , tb = self . cached_result [ 2 ]
10171043 raise val .with_traceback (tb )
10181044 else :
1045+ result = self .cached_result [0 ]
10191046 return result
10201047 # we have a previous but differently parametrized fixture instance
10211048 # so we need to tear it down before creating a new one
10221049 self .finish (request )
10231050 assert self .cached_result is None
10241051
10251052 hook = self ._fixturemanager .session .gethookproxy (request .node .fspath )
1026- return hook .pytest_fixture_setup (fixturedef = self , request = request )
1053+ result = hook .pytest_fixture_setup (fixturedef = self , request = request )
1054+ return result
10271055
10281056 def cache_key (self , request : SubRequest ) -> object :
10291057 return request .param_index if not hasattr (request , "param" ) else request .param
@@ -1034,15 +1062,17 @@ def __repr__(self) -> str:
10341062 )
10351063
10361064
1037- def resolve_fixture_function (fixturedef : FixtureDef , request : FixtureRequest ):
1065+ def resolve_fixture_function (
1066+ fixturedef : FixtureDef [_FixtureValue ], request : FixtureRequest
1067+ ) -> "_FixtureFunc[_FixtureValue]" :
10381068 """Gets the actual callable that can be called to obtain the fixture value, dealing with unittest-specific
10391069 instances and bound methods.
10401070 """
10411071 fixturefunc = fixturedef .func
10421072 if fixturedef .unittest :
10431073 if request .instance is not None :
10441074 # bind the unbound method to the TestCase instance
1045- fixturefunc = fixturedef .func .__get__ (request .instance )
1075+ fixturefunc = fixturedef .func .__get__ (request .instance ) # type: ignore[union-attr] # noqa: F821
10461076 else :
10471077 # the fixture function needs to be bound to the actual
10481078 # request.instance so that code working with "fixturedef" behaves
@@ -1051,16 +1081,18 @@ def resolve_fixture_function(fixturedef: FixtureDef, request: FixtureRequest):
10511081 # handle the case where fixture is defined not in a test class, but some other class
10521082 # (for example a plugin class with a fixture), see #2270
10531083 if hasattr (fixturefunc , "__self__" ) and not isinstance (
1054- request .instance , fixturefunc .__self__ .__class__
1084+ request .instance , fixturefunc .__self__ .__class__ # type: ignore[union-attr] # noqa: F821
10551085 ):
10561086 return fixturefunc
10571087 fixturefunc = getimfunc (fixturedef .func )
10581088 if fixturefunc != fixturedef .func :
1059- fixturefunc = fixturefunc .__get__ (request .instance )
1089+ fixturefunc = fixturefunc .__get__ (request .instance ) # type: ignore[union-attr] # noqa: F821
10601090 return fixturefunc
10611091
10621092
1063- def pytest_fixture_setup (fixturedef : FixtureDef , request : SubRequest ) -> object :
1093+ def pytest_fixture_setup (
1094+ fixturedef : FixtureDef [_FixtureValue ], request : SubRequest
1095+ ) -> _FixtureValue :
10641096 """ Execution of fixture setup. """
10651097 kwargs = {}
10661098 for argname in fixturedef .argnames :
@@ -1146,7 +1178,7 @@ class FixtureFunctionMarker:
11461178 )
11471179 name = attr .ib (type = Optional [str ], default = None )
11481180
1149- def __call__ (self , function ) :
1181+ def __call__ (self , function : _FixtureFunction ) -> _FixtureFunction :
11501182 if inspect .isclass (function ):
11511183 raise ValueError ("class fixtures not supported (maybe in the future)" )
11521184
@@ -1166,12 +1198,50 @@ def __call__(self, function):
11661198 ),
11671199 pytrace = False ,
11681200 )
1169- function ._pytestfixturefunction = self
1201+
1202+ # Type ignored because https://github.com/python/mypy/issues/2087.
1203+ function ._pytestfixturefunction = self # type: ignore[attr-defined] # noqa: F821
11701204 return function
11711205
11721206
1207+ @overload
11731208def fixture (
1174- fixture_function = None ,
1209+ fixture_function : _FixtureFunction ,
1210+ * ,
1211+ scope : "Union[_Scope, Callable[[str, Config], _Scope]]" = ...,
1212+ params : Optional [Iterable [object ]] = ...,
1213+ autouse : bool = ...,
1214+ ids : Optional [
1215+ Union [
1216+ Iterable [Union [None , str , float , int , bool ]],
1217+ Callable [[object ], Optional [object ]],
1218+ ]
1219+ ] = ...,
1220+ name : Optional [str ] = ...
1221+ ) -> _FixtureFunction :
1222+ raise NotImplementedError ()
1223+
1224+
1225+ @overload # noqa: F811
1226+ def fixture ( # noqa: F811
1227+ fixture_function : None = ...,
1228+ * ,
1229+ scope : "Union[_Scope, Callable[[str, Config], _Scope]]" = ...,
1230+ params : Optional [Iterable [object ]] = ...,
1231+ autouse : bool = ...,
1232+ ids : Optional [
1233+ Union [
1234+ Iterable [Union [None , str , float , int , bool ]],
1235+ Callable [[object ], Optional [object ]],
1236+ ]
1237+ ] = ...,
1238+ name : Optional [str ] = None
1239+ ) -> FixtureFunctionMarker :
1240+ raise NotImplementedError ()
1241+
1242+
1243+ def fixture ( # noqa: F811
1244+ fixture_function : Optional [_FixtureFunction ] = None ,
11751245 * args : Any ,
11761246 scope : "Union[_Scope, Callable[[str, Config], _Scope]]" = "function" ,
11771247 params : Optional [Iterable [object ]] = None ,
@@ -1183,7 +1253,7 @@ def fixture(
11831253 ]
11841254 ] = None ,
11851255 name : Optional [str ] = None
1186- ):
1256+ ) -> Union [ FixtureFunctionMarker , _FixtureFunction ] :
11871257 """Decorator to mark a fixture factory function.
11881258
11891259 This decorator can be used, with or without parameters, to define a
@@ -1317,7 +1387,7 @@ def yield_fixture(
13171387
13181388
13191389@fixture (scope = "session" )
1320- def pytestconfig (request : FixtureRequest ):
1390+ def pytestconfig (request : FixtureRequest ) -> Config :
13211391 """Session-scoped fixture that returns the :class:`_pytest.config.Config` object.
13221392
13231393 Example::
0 commit comments