7575if sys .version_info < (3 , 11 ):
7676 from exceptiongroup import BaseExceptionGroup
7777
78+ if sys .version_info < (3 , 10 ):
79+ from typing_extensions import ParamSpec
80+ from typing_extensions import TypeAlias
81+ else :
82+ from typing import ParamSpec
83+ from typing import TypeAlias
84+
7885
7986if TYPE_CHECKING :
8087 from _pytest .python import CallSpec2
8491
8592# The value of the fixture -- return/yield of the fixture function (type variable).
8693FixtureValue = TypeVar ("FixtureValue" )
87- # The type of the fixture function (type variable).
88- FixtureFunction = TypeVar ("FixtureFunction" , bound = Callable [..., object ])
89- # The type of a fixture function (type alias generic in fixture value).
90- _FixtureFunc = Union [
91- Callable [..., FixtureValue ], Callable [..., Generator [FixtureValue ]]
94+
95+ # The parameters that a fixture function receives.
96+ FixtureParams = ParamSpec ("FixtureParams" )
97+
98+ # The type of fixture function (type alias generic in fixture params and value).
99+ _FixtureFunc : TypeAlias = Union [
100+ Callable [FixtureParams , FixtureValue ],
101+ Callable [FixtureParams , Generator [FixtureValue , None , None ]],
92102]
93103# The type of FixtureDef.cached_result (type alias generic in fixture value).
94- _FixtureCachedResult = Union [
104+ _FixtureCachedResult : TypeAlias = Union [
95105 tuple [
96106 # The result.
97107 FixtureValue ,
@@ -121,7 +131,7 @@ def pytest_sessionstart(session: Session) -> None:
121131
122132def get_scope_package (
123133 node : nodes .Item ,
124- fixturedef : FixtureDef [object ],
134+ fixturedef : FixtureDef [Any , object ],
125135) -> nodes .Node | None :
126136 from _pytest .python import Package
127137
@@ -318,7 +328,7 @@ class FuncFixtureInfo:
318328 # matching the name which are applicable to this function.
319329 # There may be multiple overriding fixtures with the same name. The
320330 # sequence is ordered from furthest to closes to the function.
321- name2fixturedefs : dict [str , Sequence [FixtureDef [Any ]]]
331+ name2fixturedefs : dict [str , Sequence [FixtureDef [Any , Any ]]]
322332
323333 def prune_dependency_tree (self ) -> None :
324334 """Recompute names_closure from initialnames and name2fixturedefs.
@@ -359,8 +369,8 @@ def __init__(
359369 self ,
360370 pyfuncitem : Function ,
361371 fixturename : str | None ,
362- arg2fixturedefs : dict [str , Sequence [FixtureDef [Any ]]],
363- fixture_defs : dict [str , FixtureDef [Any ]],
372+ arg2fixturedefs : dict [str , Sequence [FixtureDef [Any , Any ]]],
373+ fixture_defs : dict [str , FixtureDef [Any , Any ]],
364374 * ,
365375 _ispytest : bool = False ,
366376 ) -> None :
@@ -403,7 +413,7 @@ def scope(self) -> _ScopeName:
403413 @abc .abstractmethod
404414 def _check_scope (
405415 self ,
406- requested_fixturedef : FixtureDef [object ] | PseudoFixtureDef [object ],
416+ requested_fixturedef : FixtureDef [Any , object ] | PseudoFixtureDef [object ],
407417 requested_scope : Scope ,
408418 ) -> None :
409419 raise NotImplementedError ()
@@ -544,7 +554,7 @@ def _iter_chain(self) -> Iterator[SubRequest]:
544554
545555 def _get_active_fixturedef (
546556 self , argname : str
547- ) -> FixtureDef [object ] | PseudoFixtureDef [object ]:
557+ ) -> FixtureDef [Any , object ] | PseudoFixtureDef [object ]:
548558 if argname == "request" :
549559 cached_result = (self , [0 ], None )
550560 return PseudoFixtureDef (cached_result , Scope .Function )
@@ -616,7 +626,9 @@ def _get_active_fixturedef(
616626 self ._fixture_defs [argname ] = fixturedef
617627 return fixturedef
618628
619- def _check_fixturedef_without_param (self , fixturedef : FixtureDef [object ]) -> None :
629+ def _check_fixturedef_without_param (
630+ self , fixturedef : FixtureDef [Any , object ]
631+ ) -> None :
620632 """Check that this request is allowed to execute this fixturedef without
621633 a param."""
622634 funcitem = self ._pyfuncitem
@@ -649,7 +661,7 @@ def _check_fixturedef_without_param(self, fixturedef: FixtureDef[object]) -> Non
649661 )
650662 fail (msg , pytrace = False )
651663
652- def _get_fixturestack (self ) -> list [FixtureDef [Any ]]:
664+ def _get_fixturestack (self ) -> list [FixtureDef [Any , Any ]]:
653665 values = [request ._fixturedef for request in self ._iter_chain ()]
654666 values .reverse ()
655667 return values
@@ -674,7 +686,7 @@ def _scope(self) -> Scope:
674686
675687 def _check_scope (
676688 self ,
677- requested_fixturedef : FixtureDef [object ] | PseudoFixtureDef [object ],
689+ requested_fixturedef : FixtureDef [Any , object ] | PseudoFixtureDef [object ],
678690 requested_scope : Scope ,
679691 ) -> None :
680692 # TopRequest always has function scope so always valid.
@@ -708,7 +720,7 @@ def __init__(
708720 scope : Scope ,
709721 param : Any ,
710722 param_index : int ,
711- fixturedef : FixtureDef [object ],
723+ fixturedef : FixtureDef [Any , object ],
712724 * ,
713725 _ispytest : bool = False ,
714726 ) -> None :
@@ -721,7 +733,7 @@ def __init__(
721733 )
722734 self ._parent_request : Final [FixtureRequest ] = request
723735 self ._scope_field : Final = scope
724- self ._fixturedef : Final [FixtureDef [object ]] = fixturedef
736+ self ._fixturedef : Final [FixtureDef [Any , object ]] = fixturedef
725737 if param is not NOTSET :
726738 self .param = param
727739 self .param_index : Final = param_index
@@ -751,7 +763,7 @@ def node(self):
751763
752764 def _check_scope (
753765 self ,
754- requested_fixturedef : FixtureDef [object ] | PseudoFixtureDef [object ],
766+ requested_fixturedef : FixtureDef [Any , object ] | PseudoFixtureDef [object ],
755767 requested_scope : Scope ,
756768 ) -> None :
757769 if isinstance (requested_fixturedef , PseudoFixtureDef ):
@@ -772,7 +784,7 @@ def _check_scope(
772784 pytrace = False ,
773785 )
774786
775- def _format_fixturedef_line (self , fixturedef : FixtureDef [object ]) -> str :
787+ def _format_fixturedef_line (self , fixturedef : FixtureDef [Any , object ]) -> str :
776788 factory = fixturedef .func
777789 path , lineno = getfslineno (factory )
778790 if isinstance (path , Path ):
@@ -886,7 +898,9 @@ def toterminal(self, tw: TerminalWriter) -> None:
886898
887899
888900def call_fixture_func (
889- fixturefunc : _FixtureFunc [FixtureValue ], request : FixtureRequest , kwargs
901+ fixturefunc : _FixtureFunc [FixtureParams , FixtureValue ],
902+ request : FixtureRequest ,
903+ kwargs : FixtureParams .kwargs ,
890904) -> FixtureValue :
891905 if inspect .isgeneratorfunction (fixturefunc ):
892906 fixturefunc = cast (Callable [..., Generator [FixtureValue ]], fixturefunc )
@@ -945,9 +959,11 @@ def _eval_scope_callable(
945959
946960
947961@final
948- class FixtureDef (Generic [FixtureValue ]):
962+ class FixtureDef (Generic [FixtureParams , FixtureValue ]):
949963 """A container for a fixture definition.
950964
965+ This is a generic class parametrized on the parameters that a fixture function receives and its return value.
966+
951967 Note: At this time, only explicitly documented fields and methods are
952968 considered public stable API.
953969 """
@@ -957,7 +973,7 @@ def __init__(
957973 config : Config ,
958974 baseid : str | None ,
959975 argname : str ,
960- func : _FixtureFunc [FixtureValue ],
976+ func : _FixtureFunc [FixtureParams , FixtureValue ],
961977 scope : Scope | _ScopeName | Callable [[str , Config ], _ScopeName ] | None ,
962978 params : Sequence [object ] | None ,
963979 ids : tuple [object | None , ...] | Callable [[Any ], object | None ] | None = None ,
@@ -1112,8 +1128,8 @@ def __repr__(self) -> str:
11121128
11131129
11141130def resolve_fixture_function (
1115- fixturedef : FixtureDef [FixtureValue ], request : FixtureRequest
1116- ) -> _FixtureFunc [FixtureValue ]:
1131+ fixturedef : FixtureDef [FixtureParams , FixtureValue ], request : FixtureRequest
1132+ ) -> _FixtureFunc [FixtureParams , FixtureValue ]:
11171133 """Get the actual callable that can be called to obtain the fixture
11181134 value."""
11191135 fixturefunc = fixturedef .func
@@ -1136,7 +1152,7 @@ def resolve_fixture_function(
11361152
11371153
11381154def pytest_fixture_setup (
1139- fixturedef : FixtureDef [FixtureValue ], request : SubRequest
1155+ fixturedef : FixtureDef [FixtureParams , FixtureValue ], request : SubRequest
11401156) -> FixtureValue :
11411157 """Execution of fixture setup."""
11421158 kwargs = {}
@@ -1192,7 +1208,9 @@ class FixtureFunctionMarker:
11921208 def __post_init__ (self , _ispytest : bool ) -> None :
11931209 check_ispytest (_ispytest )
11941210
1195- def __call__ (self , function : FixtureFunction ) -> FixtureFunctionDefinition :
1211+ def __call__ (
1212+ self , function : Callable [FixtureParams , FixtureValue ]
1213+ ) -> FixtureFunctionDefinition [FixtureParams , FixtureValue ]:
11961214 if inspect .isclass (function ):
11971215 raise ValueError ("class fixtures not supported (maybe in the future)" )
11981216
@@ -1219,12 +1237,10 @@ def __call__(self, function: FixtureFunction) -> FixtureFunctionDefinition:
12191237 return fixture_definition
12201238
12211239
1222- # TODO: paramspec/return type annotation tracking and storing
1223- class FixtureFunctionDefinition :
1240+ class FixtureFunctionDefinition (Generic [FixtureParams , FixtureValue ]):
12241241 def __init__ (
12251242 self ,
1226- * ,
1227- function : Callable [..., Any ],
1243+ function : Callable [FixtureParams , FixtureValue ],
12281244 fixture_function_marker : FixtureFunctionMarker ,
12291245 instance : object | None = None ,
12301246 _ispytest : bool = False ,
@@ -1237,7 +1253,7 @@ def __init__(
12371253 self ._fixture_function_marker = fixture_function_marker
12381254 if instance is not None :
12391255 self ._fixture_function = cast (
1240- Callable [..., Any ], function .__get__ (instance )
1256+ Callable [FixtureParams , FixtureValue ], function .__get__ (instance )
12411257 )
12421258 else :
12431259 self ._fixture_function = function
@@ -1246,12 +1262,14 @@ def __init__(
12461262 def __repr__ (self ) -> str :
12471263 return f"<pytest_fixture({ self ._fixture_function } )>"
12481264
1249- def __get__ (self , instance , owner = None ):
1265+ def __get__ (
1266+ self , obj : object , objtype : type | None = None
1267+ ) -> FixtureFunctionDefinition [FixtureParams , FixtureValue ]:
12501268 """Behave like a method if the function it was applied to was a method."""
12511269 return FixtureFunctionDefinition (
12521270 function = self ._fixture_function ,
12531271 fixture_function_marker = self ._fixture_function_marker ,
1254- instance = instance ,
1272+ instance = obj ,
12551273 _ispytest = True ,
12561274 )
12571275
@@ -1270,14 +1288,14 @@ def _get_wrapped_function(self) -> Callable[..., Any]:
12701288
12711289@overload
12721290def fixture (
1273- fixture_function : Callable [..., object ],
1291+ fixture_function : Callable [FixtureParams , FixtureValue ],
12741292 * ,
12751293 scope : _ScopeName | Callable [[str , Config ], _ScopeName ] = ...,
12761294 params : Iterable [object ] | None = ...,
12771295 autouse : bool = ...,
12781296 ids : Sequence [object | None ] | Callable [[Any ], object | None ] | None = ...,
12791297 name : str | None = ...,
1280- ) -> FixtureFunctionDefinition : ...
1298+ ) -> FixtureFunctionDefinition [ FixtureParams , FixtureValue ] : ...
12811299
12821300
12831301@overload
@@ -1293,14 +1311,14 @@ def fixture(
12931311
12941312
12951313def fixture (
1296- fixture_function : FixtureFunction | None = None ,
1314+ fixture_function : Callable [ FixtureParams , FixtureValue ] | None = None ,
12971315 * ,
12981316 scope : _ScopeName | Callable [[str , Config ], _ScopeName ] = "function" ,
12991317 params : Iterable [object ] | None = None ,
13001318 autouse : bool = False ,
13011319 ids : Sequence [object | None ] | Callable [[Any ], object | None ] | None = None ,
13021320 name : str | None = None ,
1303- ) -> FixtureFunctionMarker | FixtureFunctionDefinition :
1321+ ) -> FixtureFunctionMarker | FixtureFunctionDefinition [ FixtureParams , FixtureValue ] :
13041322 """Decorator to mark a fixture factory function.
13051323
13061324 This decorator can be used, with or without parameters, to define a
@@ -1507,7 +1525,7 @@ def __init__(self, session: Session) -> None:
15071525 # suite/plugins defined with this name. Populated by parsefactories().
15081526 # TODO: The order of the FixtureDefs list of each arg is significant,
15091527 # explain.
1510- self ._arg2fixturedefs : Final [dict [str , list [FixtureDef [Any ]]]] = {}
1528+ self ._arg2fixturedefs : Final [dict [str , list [FixtureDef [Any , Any ]]]] = {}
15111529 self ._holderobjseen : Final [set [object ]] = set ()
15121530 # A mapping from a nodeid to a list of autouse fixtures it defines.
15131531 self ._nodeid_autousenames : Final [dict [str , list [str ]]] = {
@@ -1598,7 +1616,7 @@ def getfixtureclosure(
15981616 parentnode : nodes .Node ,
15991617 initialnames : tuple [str , ...],
16001618 ignore_args : AbstractSet [str ],
1601- ) -> tuple [list [str ], dict [str , Sequence [FixtureDef [Any ]]]]:
1619+ ) -> tuple [list [str ], dict [str , Sequence [FixtureDef [Any , Any ]]]]:
16021620 # Collect the closure of all fixtures, starting with the given
16031621 # fixturenames as the initial set. As we have to visit all
16041622 # factory definitions anyway, we also return an arg2fixturedefs
@@ -1608,7 +1626,7 @@ def getfixtureclosure(
16081626
16091627 fixturenames_closure = list (initialnames )
16101628
1611- arg2fixturedefs : dict [str , Sequence [FixtureDef [Any ]]] = {}
1629+ arg2fixturedefs : dict [str , Sequence [FixtureDef [Any , Any ]]] = {}
16121630 lastlen = - 1
16131631 while lastlen != len (fixturenames_closure ):
16141632 lastlen = len (fixturenames_closure )
@@ -1688,7 +1706,7 @@ def _register_fixture(
16881706 self ,
16891707 * ,
16901708 name : str ,
1691- func : _FixtureFunc [object ],
1709+ func : _FixtureFunc [Any , object ],
16921710 nodeid : str | None ,
16931711 scope : Scope | _ScopeName | Callable [[str , Config ], _ScopeName ] = "function" ,
16941712 params : Sequence [object ] | None = None ,
@@ -1823,7 +1841,7 @@ def parsefactories(
18231841
18241842 def getfixturedefs (
18251843 self , argname : str , node : nodes .Node
1826- ) -> Sequence [FixtureDef [Any ]] | None :
1844+ ) -> Sequence [FixtureDef [Any , Any ]] | None :
18271845 """Get FixtureDefs for a fixture name which are applicable
18281846 to a given node.
18291847
@@ -1842,8 +1860,8 @@ def getfixturedefs(
18421860 return tuple (self ._matchfactories (fixturedefs , node ))
18431861
18441862 def _matchfactories (
1845- self , fixturedefs : Iterable [FixtureDef [Any ]], node : nodes .Node
1846- ) -> Iterator [FixtureDef [Any ]]:
1863+ self , fixturedefs : Iterable [FixtureDef [Any , Any ]], node : nodes .Node
1864+ ) -> Iterator [FixtureDef [Any , Any ]]:
18471865 parentnodeids = {n .nodeid for n in node .iter_parents ()}
18481866 for fixturedef in fixturedefs :
18491867 if fixturedef .baseid in parentnodeids :
@@ -1880,7 +1898,7 @@ def get_best_relpath(func) -> str:
18801898 loc = getlocation (func , invocation_dir )
18811899 return bestrelpath (invocation_dir , Path (loc ))
18821900
1883- def write_fixture (fixture_def : FixtureDef [object ]) -> None :
1901+ def write_fixture (fixture_def : FixtureDef [Any , object ]) -> None :
18841902 argname = fixture_def .argname
18851903 if verbose <= 0 and argname .startswith ("_" ):
18861904 return
0 commit comments