1
+ import abc
1
2
import dataclasses
2
3
import functools
3
4
import inspect
@@ -340,26 +341,32 @@ def prune_dependency_tree(self) -> None:
340
341
self .names_closure [:] = sorted (closure , key = self .names_closure .index )
341
342
342
343
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.
345
346
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.
349
349
"""
350
350
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 :
352
361
check_ispytest (_ispytest )
353
362
#: 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
358
365
# The FixtureDefs for each fixture name requested by this item.
359
366
# Starts from the statically-known fixturedefs resolved during
360
367
# collection. Dynamically requested fixtures (using
361
368
# `request.getfixturevalue("foo")`) are added dynamically.
362
- self ._arg2fixturedefs = pyfuncitem . _fixtureinfo . name2fixturedefs . copy ()
369
+ self ._arg2fixturedefs : Final = arg2fixturedefs
363
370
# A fixture may override another fixture with the same name, e.g. a fixture
364
371
# in a module can override a fixture in a conftest, a fixture in a class can
365
372
# override a fixture in the module, and so on.
@@ -369,10 +376,10 @@ def __init__(self, pyfuncitem: "Function", *, _ispytest: bool = False) -> None:
369
376
# The fixturedefs list in _arg2fixturedefs for a given name is ordered from
370
377
# furthest to closest, so we use negative indexing -1, -2, ... to go from
371
378
# last to first.
372
- self ._arg2index : Dict [ str , int ] = {}
379
+ self ._arg2index : Final = arg2index
373
380
# The evaluated argnames so far, mapping to the FixtureDef they resolved
374
381
# to.
375
- self ._fixture_defs : Dict [ str , FixtureDef [ Any ]] = {}
382
+ self ._fixture_defs : Final = fixture_defs
376
383
# Notes on the type of `param`:
377
384
# -`request.param` is only defined in parametrized fixtures, and will raise
378
385
# AttributeError otherwise. Python typing has no notion of "undefined", so
@@ -383,6 +390,15 @@ def __init__(self, pyfuncitem: "Function", *, _ispytest: bool = False) -> None:
383
390
# for now just using Any.
384
391
self .param : Any
385
392
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
+
386
402
@property
387
403
def scope (self ) -> _ScopeName :
388
404
"""Scope string, one of "function", "class", "module", "package", "session"."""
@@ -396,25 +412,10 @@ def fixturenames(self) -> List[str]:
396
412
return result
397
413
398
414
@property
415
+ @abc .abstractmethod
399
416
def node (self ):
400
417
"""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 ()
418
419
419
420
def _getnextfixturedef (self , argname : str ) -> "FixtureDef[Any]" :
420
421
fixturedefs = self ._arg2fixturedefs .get (argname , None )
@@ -500,11 +501,11 @@ def session(self) -> "Session":
500
501
"""Pytest session object."""
501
502
return self ._pyfuncitem .session # type: ignore[no-any-return]
502
503
504
+ @abc .abstractmethod
503
505
def addfinalizer (self , finalizer : Callable [[], object ]) -> None :
504
506
"""Add finalizer/teardown function to be called without arguments after
505
507
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 ()
508
509
509
510
def applymarker (self , marker : Union [str , MarkDecorator ]) -> None :
510
511
"""Apply a marker to a single test function invocation.
@@ -525,13 +526,6 @@ def raiseerror(self, msg: Optional[str]) -> NoReturn:
525
526
"""
526
527
raise self ._fixturemanager .FixtureLookupError (None , self , msg )
527
528
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
-
535
529
def getfixturevalue (self , argname : str ) -> Any :
536
530
"""Dynamically run a named fixture function.
537
531
@@ -665,6 +659,98 @@ def _schedule_finalizers(
665
659
finalizer = functools .partial (fixturedef .finish , request = subrequest )
666
660
subrequest .node .addfinalizer (finalizer )
667
661
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
+
668
754
def _check_scope (
669
755
self ,
670
756
argname : str ,
@@ -699,44 +785,7 @@ def _factorytraceback(self) -> List[str]:
699
785
)
700
786
return lines
701
787
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
-
737
788
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."""
740
789
self ._fixturedef .addfinalizer (finalizer )
741
790
742
791
def _schedule_finalizers (
0 commit comments