3232from typing import TypeVar
3333import warnings
3434
35- from .compat import deprecated
3635import _pytest
3736from _pytest import nodes
3837from _pytest ._code import getfslineno
4140from _pytest ._code .code import TerminalRepr
4241from _pytest ._io import TerminalWriter
4342from _pytest .compat import assert_never
43+ from _pytest .compat import deprecated
4444from _pytest .compat import get_real_func
4545from _pytest .compat import getfuncargnames
4646from _pytest .compat import getimfunc
6060from _pytest .deprecated import FIXTURE_BASEID_DEPRECATED
6161from _pytest .deprecated import FIXTURE_GETFIXTUREVALUE_DURING_TEARDOWN
6262from _pytest .deprecated import FIXTURE_NODEID_DEPRECATED
63+ from _pytest .deprecated import FIXTUREDEF_HAS_LOCATION_DEPRECATED
6364from _pytest .deprecated import PARSEFACTORIES_NODEID_DEPRECATED
6465from _pytest .deprecated import YIELD_FIXTURE
6566from _pytest .main import Session
@@ -140,9 +141,8 @@ def is_visibility_more_specific(
140141 than that of ``other``, i.e. ``candidate`` is defined on a strict descendant
141142 in the collection tree of where ``other`` is defined."""
142143 if candidate .node is None or other .node is None :
143- # Fallback for fixtures registered with a string nodeid (deprecated) or
144- # with global visibility (no node). In this case compare baseids, which
145- # are nodeid prefixes.
144+ # Fallback for fixtures registered with a string nodeid (deprecated).
145+ # In this case compare baseids, which are nodeid prefixes.
146146 # This branch can be removed once baseid deprecation is done (pytest 10).
147147 if candidate .baseid == other .baseid :
148148 return False
@@ -1056,26 +1056,27 @@ class FixtureDef(Generic[FixtureValue]):
10561056 def __init__ (
10571057 self ,
10581058 config : Config ,
1059- baseid : str | None ,
1059+ baseid : str | None | NotSetType ,
10601060 argname : str ,
10611061 func : _FixtureFunc [FixtureValue ],
10621062 scope : Scope | ScopeName | Callable [[str , Config ], ScopeName ] | None ,
10631063 params : Sequence [object ] | None ,
10641064 ids : tuple [object | None , ...] | Callable [[Any ], object | None ] | None = None ,
10651065 * ,
1066- _ispytest : bool = False ,
1066+ node : nodes . Node | NotSetType = NOTSET ,
10671067 # only used in a deprecationwarning msg, can be removed in pytest9
10681068 _autouse : bool = False ,
1069- node : nodes . Node | None = None ,
1069+ _ispytest : bool = False ,
10701070 ) -> None :
10711071 check_ispytest (_ispytest )
1072- # Emit deprecation warning if baseid string is used when node could be provided.
1073- # baseid=None (global plugins) and baseid="" (synthetic fixtures) are fine.
1074- if baseid and node is None :
1072+ # Emit deprecation warning if deprecated baseid string is used.
1073+ if node is NOTSET :
10751074 warnings .warn (FIXTURE_BASEID_DEPRECATED , stacklevel = 2 )
1075+ if baseid is NOTSET :
1076+ baseid = None
10761077 # The node where this fixture was defined, if available.
10771078 # Used for node-based matching which is more robust than string matching.
1078- self .node : Final = node
1079+ self .node : Final = node if node is not NOTSET else None
10791080 # The "base" node ID for the fixture.
10801081 #
10811082 # This is a node ID prefix. A fixture is only available to a node (e.g.
@@ -1090,11 +1091,15 @@ def __init__(
10901091 #
10911092 # For other plugins, the baseid is the empty string (always matches).
10921093 # When node is available, baseid is derived from node.nodeid.
1093- self .baseid : Final = node .nodeid if node is not None else (baseid or "" )
1094+ #
1095+ # Deprecated: replaced by ``node``.
1096+ self .baseid : Final = node .nodeid if node is not NOTSET else (baseid or "" )
10941097 # Whether the fixture was found from a node or a conftest in the
10951098 # collection tree. Will be false for fixtures defined in non-conftest
10961099 # plugins.
1097- self .has_location : Final = node is not None or baseid is not None
1100+ #
1101+ # Deprecated: kept only to back the deprecated ``has_location`` property.
1102+ self ._has_location : Final = node is not NOTSET or baseid is not None
10981103 # The fixture factory function.
10991104 self .func : Final = func
11001105 # The name by which the fixture may be requested.
@@ -1129,6 +1134,11 @@ def scope(self) -> ScopeName:
11291134 """Scope string, one of "function", "class", "module", "package", "session"."""
11301135 return self ._scope .value
11311136
1137+ @property
1138+ def has_location (self ) -> bool :
1139+ warnings .warn (FIXTUREDEF_HAS_LOCATION_DEPRECATED , stacklevel = 2 )
1140+ return self ._has_location
1141+
11321142 def addfinalizer (self , finalizer : Callable [[], object ]) -> None :
11331143 self ._finalizers .append (finalizer )
11341144
@@ -1243,11 +1253,12 @@ class RequestFixtureDef(FixtureDef[FixtureRequest]):
12431253 def __init__ (self , request : FixtureRequest ) -> None :
12441254 super ().__init__ (
12451255 config = request .config ,
1246- baseid = None ,
1256+ baseid = NOTSET ,
12471257 argname = "request" ,
12481258 func = lambda : request ,
12491259 scope = Scope .Function ,
12501260 params = None ,
1261+ node = request .node ,
12511262 _ispytest = True ,
12521263 )
12531264 self .cached_result = (request , [0 ], None )
@@ -1776,8 +1787,8 @@ def pytest_plugin_registered(self, plugin: _PluggyPlugin, plugin_name: str) -> N
17761787 # Store conftest for deferred parsing when its Directory is collected.
17771788 self ._pending_conftests [conftest_dir ] = plugin
17781789 else :
1779- # Non-conftest plugins have global visibility (nodeid=None) .
1780- self .parsefactories (plugin , None )
1790+ # Non-conftest plugins have global visibility.
1791+ self .parsefactories (holder = plugin , node = self . session )
17811792
17821793 @hookimpl (wrapper = True )
17831794 def pytest_make_collect_report (
@@ -1926,12 +1937,12 @@ def _register_fixture(
19261937 * ,
19271938 name : str ,
19281939 func : _FixtureFunc [object ],
1929- nodeid : str | None = None ,
1940+ nodeid : str | None | NotSetType = NOTSET ,
19301941 scope : Scope | ScopeName | Callable [[str , Config ], ScopeName ] = "function" ,
19311942 params : Sequence [object ] | None = None ,
19321943 ids : tuple [object | None , ...] | Callable [[Any ], object | None ] | None = None ,
19331944 autouse : bool = False ,
1934- node : nodes .Node | None = None ,
1945+ node : nodes .Node | NotSetType = NOTSET ,
19351946 ) -> None :
19361947 """Register a fixture
19371948
@@ -1955,13 +1966,12 @@ def _register_fixture(
19551966 :param autouse:
19561967 Whether this is an autouse fixture.
19571968 """
1958- # Emit deprecation warning if nodeid string is used when node could be provided.
1959- # nodeid=None (global plugins) is fine.
1960- if nodeid and node is None :
1969+ # Emit deprecation warning if nodeid string.
1970+ if nodeid is not NOTSET or node is NOTSET :
19611971 warnings .warn (FIXTURE_NODEID_DEPRECATED , stacklevel = 2 )
19621972 fixture_def = FixtureDef (
19631973 config = self .config ,
1964- baseid = nodeid if node is None else None ,
1974+ baseid = nodeid ,
19651975 argname = name ,
19661976 func = func ,
19671977 scope = scope ,
@@ -1991,9 +2001,9 @@ def _register_fixture(
19912001 else :
19922002 faclist .append (fixture_def )
19932003 if autouse :
1994- if node is not None :
2004+ if node is not NOTSET :
19952005 self ._node_autousenames .setdefault (node , []).append (name )
1996- elif nodeid :
2006+ elif nodeid is not NOTSET and nodeid is not None :
19972007 # Legacy: plugin passed nodeid string without node reference.
19982008 self ._nodeid_autousenames .setdefault (nodeid , []).append (name )
19992009 else :
@@ -2008,6 +2018,9 @@ def parsefactories(
20082018 raise NotImplementedError ()
20092019
20102020 @overload
2021+ @deprecated (
2022+ "parsefactories(obj, nodeid) is deprecated, use parsefactories(holder=obj, node=node) instead"
2023+ )
20112024 def parsefactories (
20122025 self ,
20132026 node_or_obj : object ,
@@ -2018,8 +2031,8 @@ def parsefactories(
20182031 @overload
20192032 def parsefactories (
20202033 self ,
2021- node_or_obj : None = ...,
2022- nodeid : None = ...,
2034+ node_or_obj : NotSetType = ...,
2035+ nodeid : NotSetType = ...,
20232036 * ,
20242037 holder : object ,
20252038 node : nodes .Node ,
@@ -2028,44 +2041,42 @@ def parsefactories(
20282041
20292042 def parsefactories (
20302043 self ,
2031- node_or_obj : nodes .Node | object | None = None ,
2032- nodeid : str | NotSetType | None = NOTSET ,
2044+ node_or_obj : nodes .Node | object | NotSetType = NOTSET ,
2045+ nodeid : str | None | NotSetType = NOTSET ,
20332046 * ,
2034- holder : object | None = None ,
2035- node : nodes .Node | None = None ,
2047+ holder : object | NotSetType = NOTSET ,
2048+ node : nodes .Node | NotSetType = NOTSET ,
20362049 ) -> None :
20372050 """Collect fixtures from a collection node or object.
20382051
20392052 Found fixtures are parsed into `FixtureDef`s and saved.
20402053
20412054 The preferred API uses keyword-only arguments:
20422055 - ``holder``: The object to scan for fixtures.
2043- - ``node``: The node determining fixture visibility scope .
2056+ - ``node``: The node determining fixture visibility.
20442057
20452058 Legacy positional API (translated internally):
20462059 - ``parsefactories(node)``: Uses node.obj as holder, node for scope.
20472060 - ``parsefactories(obj, nodeid)``: Uses obj as holder, nodeid string for scope.
20482061 """
20492062 # Translate legacy API to holder/node sources of truth
20502063 # Either effective_node or effective_nodeid will be set, not both
2051- effective_node : nodes .Node | None = None
2052- effective_nodeid : str | None = None
2064+ effective_node : nodes .Node | NotSetType = NOTSET
2065+ effective_nodeid : str | None | NotSetType = NOTSET
20532066
2054- if holder is not None :
2067+ if holder is not NOTSET :
20552068 # New API: holder and node explicitly provided
20562069 holderobj = holder
20572070 effective_node = node
2058- elif node_or_obj is None :
2071+ elif node_or_obj is NOTSET :
20592072 raise TypeError ("parsefactories() requires holder or node_or_obj" )
20602073 elif nodeid is not NOTSET :
2061- # Legacy: parsefactories(obj, nodeid) - string-based scoping only
2062- # Only warn if a non-None nodeid string is passed (None means global plugin)
2063- if nodeid is not None :
2064- warnings .warn (PARSEFACTORIES_NODEID_DEPRECATED , stacklevel = 2 )
2074+ # Legacy: parsefactories(obj, nodeid) - string-based scoping only.
2075+ warnings .warn (PARSEFACTORIES_NODEID_DEPRECATED , stacklevel = 2 )
20652076 holderobj = node_or_obj
20662077 effective_nodeid = nodeid
20672078 else :
2068- # Legacy: parsefactories(node) - node has .obj attribute
2079+ # parsefactories(node) - node has .obj attribute
20692080 assert isinstance (node_or_obj , nodes .Node )
20702081 holderobj = cast (object , node_or_obj .obj ) # type: ignore[attr-defined]
20712082 effective_node = node_or_obj
0 commit comments