1+ import abc
12import dataclasses
23import functools
34import inspect
@@ -340,26 +341,32 @@ def prune_dependency_tree(self) -> None:
340341 self .names_closure [:] = sorted (closure , key = self .names_closure .index )
341342
342343
343- class FixtureRequest :
344- """A request for a fixture from a test or fixture function .
344+ class FixtureRequest ( abc . ABC ) :
345+ """The type of the ``request`` fixture.
345346
346- A request object gives access to the requesting test context and has
347- an optional ``param`` attribute in case the fixture is parametrized
348- indirectly.
347+ A request object gives access to the requesting test context and has a
348+ ``param`` attribute in case the fixture is parametrized.
349349 """
350350
351- def __init__ (self , pyfuncitem : "Function" , * , _ispytest : bool = False ) -> None :
351+ def __init__ (
352+ self ,
353+ pyfuncitem : "Function" ,
354+ fixturename : Optional [str ],
355+ arg2fixturedefs : Dict [str , Sequence ["FixtureDef[Any]" ]],
356+ arg2index : Dict [str , int ],
357+ fixture_defs : Dict [str , "FixtureDef[Any]" ],
358+ * ,
359+ _ispytest : bool = False ,
360+ ) -> None :
352361 check_ispytest (_ispytest )
353362 #: Fixture for which this request is being performed.
354- self .fixturename : Optional [str ] = None
355- self ._pyfuncitem = pyfuncitem
356- self ._fixturemanager = pyfuncitem .session ._fixturemanager
357- self ._scope = Scope .Function
363+ self .fixturename : Final = fixturename
364+ self ._pyfuncitem : Final = pyfuncitem
358365 # The FixtureDefs for each fixture name requested by this item.
359366 # Starts from the statically-known fixturedefs resolved during
360367 # collection. Dynamically requested fixtures (using
361368 # `request.getfixturevalue("foo")`) are added dynamically.
362- self ._arg2fixturedefs = pyfuncitem . _fixtureinfo . name2fixturedefs . copy ()
369+ self ._arg2fixturedefs : Final = arg2fixturedefs
363370 # A fixture may override another fixture with the same name, e.g. a fixture
364371 # in a module can override a fixture in a conftest, a fixture in a class can
365372 # override a fixture in the module, and so on.
@@ -369,10 +376,10 @@ def __init__(self, pyfuncitem: "Function", *, _ispytest: bool = False) -> None:
369376 # The fixturedefs list in _arg2fixturedefs for a given name is ordered from
370377 # furthest to closest, so we use negative indexing -1, -2, ... to go from
371378 # last to first.
372- self ._arg2index : Dict [ str , int ] = {}
379+ self ._arg2index : Final = arg2index
373380 # The evaluated argnames so far, mapping to the FixtureDef they resolved
374381 # to.
375- self ._fixture_defs : Dict [ str , FixtureDef [ Any ]] = {}
382+ self ._fixture_defs : Final = fixture_defs
376383 # Notes on the type of `param`:
377384 # -`request.param` is only defined in parametrized fixtures, and will raise
378385 # AttributeError otherwise. Python typing has no notion of "undefined", so
@@ -383,6 +390,15 @@ def __init__(self, pyfuncitem: "Function", *, _ispytest: bool = False) -> None:
383390 # for now just using Any.
384391 self .param : Any
385392
393+ @property
394+ def _fixturemanager (self ) -> "FixtureManager" :
395+ return self ._pyfuncitem .session ._fixturemanager
396+
397+ @property
398+ @abc .abstractmethod
399+ def _scope (self ) -> Scope :
400+ raise NotImplementedError ()
401+
386402 @property
387403 def scope (self ) -> _ScopeName :
388404 """Scope string, one of "function", "class", "module", "package", "session"."""
@@ -396,25 +412,10 @@ def fixturenames(self) -> List[str]:
396412 return result
397413
398414 @property
415+ @abc .abstractmethod
399416 def node (self ):
400417 """Underlying collection node (depends on current request scope)."""
401- scope = self ._scope
402- if scope is Scope .Function :
403- # This might also be a non-function Item despite its attribute name.
404- node : Optional [Union [nodes .Item , nodes .Collector ]] = self ._pyfuncitem
405- elif scope is Scope .Package :
406- # FIXME: _fixturedef is not defined on FixtureRequest (this class),
407- # but on SubRequest (a subclass).
408- node = get_scope_package (self ._pyfuncitem , self ._fixturedef ) # type: ignore[attr-defined]
409- else :
410- node = get_scope_node (self ._pyfuncitem , scope )
411- if node is None and scope is Scope .Class :
412- # Fallback to function item itself.
413- node = self ._pyfuncitem
414- assert node , 'Could not obtain a node for scope "{}" for function {!r}' .format (
415- scope , self ._pyfuncitem
416- )
417- return node
418+ raise NotImplementedError ()
418419
419420 def _getnextfixturedef (self , argname : str ) -> "FixtureDef[Any]" :
420421 fixturedefs = self ._arg2fixturedefs .get (argname , None )
@@ -500,11 +501,11 @@ def session(self) -> "Session":
500501 """Pytest session object."""
501502 return self ._pyfuncitem .session # type: ignore[no-any-return]
502503
504+ @abc .abstractmethod
503505 def addfinalizer (self , finalizer : Callable [[], object ]) -> None :
504506 """Add finalizer/teardown function to be called without arguments after
505507 the last test within the requesting test context finished execution."""
506- # XXX usually this method is shadowed by fixturedef specific ones.
507- self .node .addfinalizer (finalizer )
508+ raise NotImplementedError ()
508509
509510 def applymarker (self , marker : Union [str , MarkDecorator ]) -> None :
510511 """Apply a marker to a single test function invocation.
@@ -525,13 +526,6 @@ def raiseerror(self, msg: Optional[str]) -> NoReturn:
525526 """
526527 raise self ._fixturemanager .FixtureLookupError (None , self , msg )
527528
528- def _fillfixtures (self ) -> None :
529- item = self ._pyfuncitem
530- fixturenames = getattr (item , "fixturenames" , self .fixturenames )
531- for argname in fixturenames :
532- if argname not in item .funcargs :
533- item .funcargs [argname ] = self .getfixturevalue (argname )
534-
535529 def getfixturevalue (self , argname : str ) -> Any :
536530 """Dynamically run a named fixture function.
537531
@@ -665,6 +659,98 @@ def _schedule_finalizers(
665659 finalizer = functools .partial (fixturedef .finish , request = subrequest )
666660 subrequest .node .addfinalizer (finalizer )
667661
662+
663+ @final
664+ class TopRequest (FixtureRequest ):
665+ """The type of the ``request`` fixture in a test function."""
666+
667+ def __init__ (self , pyfuncitem : "Function" , * , _ispytest : bool = False ) -> None :
668+ super ().__init__ (
669+ fixturename = None ,
670+ pyfuncitem = pyfuncitem ,
671+ arg2fixturedefs = pyfuncitem ._fixtureinfo .name2fixturedefs .copy (),
672+ arg2index = {},
673+ fixture_defs = {},
674+ _ispytest = _ispytest ,
675+ )
676+
677+ @property
678+ def _scope (self ) -> Scope :
679+ return Scope .Function
680+
681+ @property
682+ def node (self ):
683+ return self ._pyfuncitem
684+
685+ def __repr__ (self ) -> str :
686+ return "<FixtureRequest for %r>" % (self .node )
687+
688+ def _fillfixtures (self ) -> None :
689+ item = self ._pyfuncitem
690+ fixturenames = getattr (item , "fixturenames" , self .fixturenames )
691+ for argname in fixturenames :
692+ if argname not in item .funcargs :
693+ item .funcargs [argname ] = self .getfixturevalue (argname )
694+
695+ def addfinalizer (self , finalizer : Callable [[], object ]) -> None :
696+ self .node .addfinalizer (finalizer )
697+
698+
699+ @final
700+ class SubRequest (FixtureRequest ):
701+ """The type of the ``request`` fixture in a fixture function requested
702+ (transitively) by a test function."""
703+
704+ def __init__ (
705+ self ,
706+ request : FixtureRequest ,
707+ scope : Scope ,
708+ param : Any ,
709+ param_index : int ,
710+ fixturedef : "FixtureDef[object]" ,
711+ * ,
712+ _ispytest : bool = False ,
713+ ) -> None :
714+ super ().__init__ (
715+ pyfuncitem = request ._pyfuncitem ,
716+ fixturename = fixturedef .argname ,
717+ fixture_defs = request ._fixture_defs ,
718+ arg2fixturedefs = request ._arg2fixturedefs ,
719+ arg2index = request ._arg2index ,
720+ _ispytest = _ispytest ,
721+ )
722+ self ._parent_request : Final [FixtureRequest ] = request
723+ self ._scope_field : Final = scope
724+ self ._fixturedef : Final = fixturedef
725+ if param is not NOTSET :
726+ self .param = param
727+ self .param_index : Final = param_index
728+
729+ def __repr__ (self ) -> str :
730+ return f"<SubRequest { self .fixturename !r} for { self ._pyfuncitem !r} >"
731+
732+ @property
733+ def _scope (self ) -> Scope :
734+ return self ._scope_field
735+
736+ @property
737+ def node (self ):
738+ scope = self ._scope
739+ if scope is Scope .Function :
740+ # This might also be a non-function Item despite its attribute name.
741+ node : Optional [Union [nodes .Item , nodes .Collector ]] = self ._pyfuncitem
742+ elif scope is Scope .Package :
743+ node = get_scope_package (self ._pyfuncitem , self ._fixturedef )
744+ else :
745+ node = get_scope_node (self ._pyfuncitem , scope )
746+ if node is None and scope is Scope .Class :
747+ # Fallback to function item itself.
748+ node = self ._pyfuncitem
749+ assert node , 'Could not obtain a node for scope "{}" for function {!r}' .format (
750+ scope , self ._pyfuncitem
751+ )
752+ return node
753+
668754 def _check_scope (
669755 self ,
670756 argname : str ,
@@ -699,44 +785,7 @@ def _factorytraceback(self) -> List[str]:
699785 )
700786 return lines
701787
702- def __repr__ (self ) -> str :
703- return "<FixtureRequest for %r>" % (self .node )
704-
705-
706- @final
707- class SubRequest (FixtureRequest ):
708- """A sub request for handling getting a fixture from a test function/fixture."""
709-
710- def __init__ (
711- self ,
712- request : "FixtureRequest" ,
713- scope : Scope ,
714- param : Any ,
715- param_index : int ,
716- fixturedef : "FixtureDef[object]" ,
717- * ,
718- _ispytest : bool = False ,
719- ) -> None :
720- check_ispytest (_ispytest )
721- self ._parent_request = request
722- self .fixturename = fixturedef .argname
723- if param is not NOTSET :
724- self .param = param
725- self .param_index = param_index
726- self ._scope = scope
727- self ._fixturedef = fixturedef
728- self ._pyfuncitem = request ._pyfuncitem
729- self ._fixture_defs = request ._fixture_defs
730- self ._arg2fixturedefs = request ._arg2fixturedefs
731- self ._arg2index = request ._arg2index
732- self ._fixturemanager = request ._fixturemanager
733-
734- def __repr__ (self ) -> str :
735- return f"<SubRequest { self .fixturename !r} for { self ._pyfuncitem !r} >"
736-
737788 def addfinalizer (self , finalizer : Callable [[], object ]) -> None :
738- """Add finalizer/teardown function to be called without arguments after
739- the last test within the requesting test context finished execution."""
740789 self ._fixturedef .addfinalizer (finalizer )
741790
742791 def _schedule_finalizers (
0 commit comments