26
26
from _pytest .config import Config
27
27
from _pytest .config import ConftestImportFailure
28
28
from _pytest .deprecated import FSCOLLECTOR_GETHOOKPROXY_ISINITPATH
29
+ from _pytest .deprecated import NODE_IMPLIED_ARG
29
30
from _pytest .mark .structures import Mark
30
31
from _pytest .mark .structures import MarkDecorator
31
32
from _pytest .mark .structures import NodeKeywords
@@ -110,45 +111,68 @@ class Node(metaclass=NodeMeta):
110
111
"parent" ,
111
112
"config" ,
112
113
"session" ,
113
- "fspath " ,
114
+ "fs_path " ,
114
115
"_nodeid" ,
115
116
"_store" ,
116
117
"__dict__" ,
117
118
)
118
119
120
+ name : str
121
+ parent : Optional ["Node" ]
122
+ config : Config
123
+ session : "Session"
124
+ fs_path : Path
125
+ _nodeid : str
126
+
119
127
def __init__ (
120
128
self ,
129
+ * ,
121
130
name : str ,
122
- parent : " Optional[Node]" = None ,
131
+ parent : Optional [" Node" ] ,
123
132
config : Optional [Config ] = None ,
124
- session : " Optional[Session]" = None ,
125
- fspath : Optional [py . path . local ] = None ,
133
+ session : Optional [" Session" ] = None ,
134
+ fs_path : Optional [Path ] = None ,
126
135
nodeid : Optional [str ] = None ,
127
136
) -> None :
128
137
#: A unique name within the scope of the parent node.
129
138
self .name = name
130
139
140
+ if nodeid is not None :
141
+ assert "::()" not in nodeid
142
+ else :
143
+ assert parent is not None
144
+ nodeid = parent .nodeid
145
+ if name != "()" :
146
+ nodeid = f"{ nodeid } ::{ name } "
147
+ warnings .warn (NODE_IMPLIED_ARG .format (type = type (self ), arg = "nodeid" ))
148
+ self ._nodeid = nodeid
131
149
#: The parent collector node.
132
150
self .parent = parent
133
151
134
152
#: The pytest config object.
135
- if config :
136
- self .config : Config = config
137
- else :
138
- if not parent :
139
- raise TypeError ("config or parent must be provided" )
153
+ if config is None :
154
+ assert parent is not None
140
155
self .config = parent .config
156
+ warnings .warn (NODE_IMPLIED_ARG .format (type = type (self ), arg = "config" ))
157
+ else :
158
+ self .config = config
141
159
142
160
#: The pytest session this node is part of.
143
- if session :
144
- self .session = session
145
- else :
146
- if not parent :
147
- raise TypeError ("session or parent must be provided" )
161
+ if session is None :
162
+ assert parent is not None
148
163
self .session = parent .session
149
164
165
+ warnings .warn (NODE_IMPLIED_ARG .format (type = type (self ), arg = "session" ))
166
+ else :
167
+ self .session = session
168
+
150
169
#: Filesystem path where this node was collected from (can be None).
151
- self .fspath = fspath or getattr (parent , "fspath" , None )
170
+ if fs_path is None :
171
+ assert parent is not None
172
+ self .fs_path = parent .fs_path
173
+ warnings .warn (NODE_IMPLIED_ARG .format (type = type (self ), arg = "fs_path" ))
174
+ else :
175
+ self .fs_path = fs_path
152
176
153
177
# The explicit annotation is to avoid publicly exposing NodeKeywords.
154
178
#: Keywords/markers collected from all scopes.
@@ -160,22 +184,29 @@ def __init__(
160
184
#: Allow adding of extra keywords to use for matching.
161
185
self .extra_keyword_matches : Set [str ] = set ()
162
186
163
- if nodeid is not None :
164
- assert "::()" not in nodeid
165
- self ._nodeid = nodeid
166
- else :
167
- if not self .parent :
168
- raise TypeError ("nodeid or parent must be provided" )
169
- self ._nodeid = self .parent .nodeid
170
- if self .name != "()" :
171
- self ._nodeid += "::" + self .name
172
-
173
187
# A place where plugins can store information on the node for their
174
188
# own use. Currently only intended for internal plugins.
175
189
self ._store = Store ()
176
190
191
+ @property
192
+ def fspath (self ) -> py .path .local :
193
+ """Filesystem path where this node was collected from (can be None).
194
+
195
+ Since pytest 6.2, prefer to use `fs_path` instead which returns a `pathlib.Path`
196
+ instead of a `py.path.local`.
197
+ """
198
+ return py .path .local (self .fs_path )
199
+
177
200
@classmethod
178
- def from_parent (cls , parent : "Node" , ** kw ):
201
+ def from_parent (
202
+ cls ,
203
+ parent : "Node" ,
204
+ * ,
205
+ name : str ,
206
+ fs_path : Optional [Path ] = None ,
207
+ nodeid : Optional [str ] = None ,
208
+ ** kw : Any ,
209
+ ):
179
210
"""Public constructor for Nodes.
180
211
181
212
This indirection got introduced in order to enable removing
@@ -190,7 +221,26 @@ def from_parent(cls, parent: "Node", **kw):
190
221
raise TypeError ("config is not a valid argument for from_parent" )
191
222
if "session" in kw :
192
223
raise TypeError ("session is not a valid argument for from_parent" )
193
- return cls ._create (parent = parent , ** kw )
224
+ if nodeid is not None :
225
+ assert "::()" not in nodeid
226
+ else :
227
+ nodeid = parent .nodeid
228
+ if name != "()" :
229
+ nodeid = f"{ nodeid } ::{ name } "
230
+
231
+ config = parent .config
232
+ session = parent .session
233
+ if fs_path is None :
234
+ fs_path = parent .fs_path
235
+ return cls ._create (
236
+ parent = parent ,
237
+ config = config ,
238
+ session = session ,
239
+ nodeid = nodeid ,
240
+ name = name ,
241
+ fs_path = fs_path ,
242
+ ** kw ,
243
+ )
194
244
195
245
@property
196
246
def ihook (self ):
@@ -495,38 +545,58 @@ def _check_initialpaths_for_relpath(
495
545
496
546
497
547
class FSCollector (Collector ):
498
- def __init__ (
499
- self ,
500
- fspath : py .path .local ,
501
- parent = None ,
502
- config : Optional [Config ] = None ,
503
- session : Optional ["Session" ] = None ,
548
+ def __init__ (self , ** kw ):
549
+
550
+ fs_path : Optional [Path ] = kw .pop ("fs_path" , None )
551
+ fspath : Optional [py .path .local ] = kw .pop ("fspath" , None )
552
+
553
+ if fspath is not None :
554
+ assert fs_path is None
555
+ fs_path = Path (fspath )
556
+ kw ["fs_path" ] = fs_path
557
+ super ().__init__ (** kw )
558
+
559
+ @classmethod
560
+ def from_parent (
561
+ cls ,
562
+ parent : Node ,
563
+ * ,
564
+ fspath : Optional [py .path .local ] = None ,
565
+ fs_path : Optional [Path ] = None ,
504
566
nodeid : Optional [str ] = None ,
505
- ) -> None :
506
- name = fspath .basename
507
- if parent is not None :
567
+ name : Optional [str ] = None ,
568
+ ** kw ,
569
+ ):
570
+ """The public constructor."""
571
+ if fspath is not None :
572
+ assert fs_path is None
573
+ known_path = Path (fspath )
574
+ else :
575
+ assert fs_path is not None
576
+ known_path = fs_path
577
+ fspath = py .path .local (known_path )
578
+
579
+ if name is None :
580
+ name = known_path .name
508
581
rel = fspath .relto (parent .fspath )
509
582
if rel :
510
- name = rel
511
- name = name .replace (os .sep , SEP )
512
- self .fspath = fspath
513
-
514
- session = session or parent .session
583
+ name = str (rel )
515
584
516
585
if nodeid is None :
517
- nodeid = self .fspath .relto (session .config .rootdir )
586
+ assert parent is not None
587
+ session = parent .session
588
+ relp = session ._bestrelpathcache [known_path ]
589
+ if not relp .startswith (".." + os .sep ):
590
+ nodeid = str (relp )
518
591
519
592
if not nodeid :
520
593
nodeid = _check_initialpaths_for_relpath (session , fspath )
521
594
if nodeid and os .sep != SEP :
522
595
nodeid = nodeid .replace (os .sep , SEP )
523
596
524
- super ().__init__ (name , parent , config , session , nodeid = nodeid , fspath = fspath )
525
-
526
- @classmethod
527
- def from_parent (cls , parent , * , fspath , ** kw ):
528
- """The public constructor."""
529
- return super ().from_parent (parent = parent , fspath = fspath , ** kw )
597
+ return super ().from_parent (
598
+ parent = parent , name = name , fs_path = known_path , nodeid = nodeid , ** kw
599
+ )
530
600
531
601
def gethookproxy (self , fspath : "os.PathLike[str]" ):
532
602
warnings .warn (FSCOLLECTOR_GETHOOKPROXY_ISINITPATH , stacklevel = 2 )
@@ -555,12 +625,20 @@ class Item(Node):
555
625
def __init__ (
556
626
self ,
557
627
name ,
558
- parent = None ,
559
- config : Optional [Config ] = None ,
560
- session : Optional ["Session" ] = None ,
561
- nodeid : Optional [str ] = None ,
628
+ parent : Node ,
629
+ config : Config ,
630
+ session : "Session" ,
631
+ nodeid : str ,
632
+ fs_path : Path ,
562
633
) -> None :
563
- super ().__init__ (name , parent , config , session , nodeid = nodeid )
634
+ super ().__init__ (
635
+ name = name ,
636
+ parent = parent ,
637
+ config = config ,
638
+ session = session ,
639
+ nodeid = nodeid ,
640
+ fs_path = fs_path ,
641
+ )
564
642
self ._report_sections : List [Tuple [str , str , str ]] = []
565
643
566
644
#: A list of tuples (name, value) that holds user defined properties
0 commit comments