Skip to content

Commit f4fa848

Browse files
committed
WIP: Revert "Node from parent (pytest-dev#5975)"
Keeps `Node.from_parent` for backward compatibility. This reverts commit a3c8246, reversing changes made to 886b8d2. Ref: pytest-dev#5975 (comment)
1 parent 7fb8e49 commit f4fa848

File tree

17 files changed

+43
-139
lines changed

17 files changed

+43
-139
lines changed

changelog/5975.deprecation.rst

Lines changed: 0 additions & 10 deletions
This file was deleted.

doc/en/deprecations.rst

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -33,16 +33,6 @@ provide feedback.
3333
display captured output when tests fail: ``no``, ``stdout``, ``stderr``, ``log`` or ``all`` (the default).
3434

3535

36-
37-
Node Construction changed to ``Node.from_parent``
38-
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
39-
40-
.. deprecated:: 5.3
41-
42-
The construction of nodes new should use the named constructor ``from_parent``.
43-
This limitation in api surface intends to enable better/simpler refactoring of the collection tree.
44-
45-
4636
``junit_family`` default value change to "xunit2"
4737
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4838

doc/en/example/nonpython/conftest.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
def pytest_collect_file(parent, path):
66
if path.ext == ".yaml" and path.basename.startswith("test"):
7-
return YamlFile.from_parent(parent, fspath=path)
7+
return YamlFile(path, parent)
88

99

1010
class YamlFile(pytest.File):
@@ -13,7 +13,7 @@ def collect(self):
1313

1414
raw = yaml.safe_load(self.fspath.open())
1515
for name, spec in sorted(raw.items()):
16-
yield YamlItem.from_parent(self, name=name, spec=spec)
16+
yield YamlItem(name, self, spec)
1717

1818

1919
class YamlItem(pytest.Item):

src/_pytest/deprecated.py

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,6 @@
4040
"as a keyword argument instead."
4141
)
4242

43-
NODE_USE_FROM_PARENT = UnformattedWarning(
44-
PytestDeprecationWarning,
45-
"direct construction of {name} has been deprecated, please use {name}.from_parent",
46-
)
47-
4843
JUNIT_XML_DEFAULT_FAMILY = PytestDeprecationWarning(
4944
"The 'junit_family' default value will change to 'xunit2' in pytest 6.0.\n"
5045
"Add 'junit_family=xunit1' to your pytest.ini file to keep the current format "

src/_pytest/doctest.py

Lines changed: 4 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -112,9 +112,9 @@ def pytest_collect_file(path, parent):
112112
config = parent.config
113113
if path.ext == ".py":
114114
if config.option.doctestmodules and not _is_setup_py(config, path, parent):
115-
return DoctestModule.from_parent(parent, fspath=path)
115+
return DoctestModule(path, parent)
116116
elif _is_doctest(config, path, parent):
117-
return DoctestTextfile.from_parent(parent, fspath=path)
117+
return DoctestTextfile(path, parent)
118118

119119

120120
def _is_setup_py(config, path, parent):
@@ -221,16 +221,6 @@ def __init__(self, name, parent, runner=None, dtest=None):
221221
self.obj = None
222222
self.fixture_request = None
223223

224-
@classmethod
225-
def from_parent( # type: ignore
226-
cls, parent: "Union[DoctestTextfile, DoctestModule]", *, name, runner, dtest
227-
):
228-
# incompatible signature due to to imposed limits on sublcass
229-
"""
230-
the public named constructor
231-
"""
232-
return super().from_parent(name=name, parent=parent, runner=runner, dtest=dtest)
233-
234224
def setup(self):
235225
if self.dtest is not None:
236226
self.fixture_request = _setup_fixtures(self)
@@ -389,9 +379,7 @@ def collect(self):
389379
parser = doctest.DocTestParser()
390380
test = parser.get_doctest(text, globs, name, filename, 0)
391381
if test.examples:
392-
yield DoctestItem.from_parent(
393-
self, name=test.name, runner=runner, dtest=test
394-
)
382+
yield DoctestItem(test.name, self, runner, test)
395383

396384

397385
def _check_all_skipped(test):
@@ -500,9 +488,7 @@ def _find(
500488

501489
for test in finder.find(module, module.__name__):
502490
if test.examples: # skip empty doctests
503-
yield DoctestItem.from_parent(
504-
self, name=test.name, runner=runner, dtest=test
505-
)
491+
yield DoctestItem(test.name, self, runner, test)
506492

507493

508494
def _setup_fixtures(doctest_item):

src/_pytest/main.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ def wrap_session(
183183
config: Config, doit: Callable[[Config, "Session"], Optional[Union[int, ExitCode]]]
184184
) -> Union[int, ExitCode]:
185185
"""Skeleton command line program"""
186-
session = Session.from_config(config)
186+
session = Session(config)
187187
session.exitstatus = ExitCode.OK
188188
initstate = 0
189189
try:
@@ -387,10 +387,6 @@ def __init__(self, config: Config) -> None:
387387

388388
self._deselected = [] # type: List[nodes.Item]
389389

390-
@classmethod
391-
def from_config(cls, config):
392-
return cls._create(config)
393-
394390
def __repr__(self):
395391
return "<%s %s exitstatus=%r testsfailed=%d testscollected=%d>" % (
396392
self.__class__.__name__,

src/_pytest/nodes.py

Lines changed: 10 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
from _pytest.compat import TYPE_CHECKING
2121
from _pytest.config import Config
2222
from _pytest.config import PytestPluginManager
23-
from _pytest.deprecated import NODE_USE_FROM_PARENT
2423
from _pytest.fixtures import FixtureDef
2524
from _pytest.fixtures import FixtureLookupError
2625
from _pytest.fixtures import FixtureLookupErrorRepr
@@ -31,10 +30,13 @@
3130
from _pytest.outcomes import Failed
3231

3332
if TYPE_CHECKING:
34-
# Imported here due to circular import.
35-
from _pytest.main import Session # noqa: F401
33+
from typing import Type
34+
from typing import TypeVar
3635

37-
from _pytest._code.code import _TracebackStyle # noqa: F401
36+
from _pytest._code.code import _TracebackStyle
37+
from _pytest.main import Session # circular import
38+
39+
_N = TypeVar("_N", bound="Node")
3840

3941
SEP = "/"
4042

@@ -79,16 +81,7 @@ def ischildnode(baseid, nodeid):
7981
return node_parts[: len(base_parts)] == base_parts
8082

8183

82-
class NodeMeta(type):
83-
def __call__(self, *k, **kw):
84-
warnings.warn(NODE_USE_FROM_PARENT.format(name=self.__name__), stacklevel=2)
85-
return super().__call__(*k, **kw)
86-
87-
def _create(self, *k, **kw):
88-
return super().__call__(*k, **kw)
89-
90-
91-
class Node(metaclass=NodeMeta):
84+
class Node:
9285
""" base class for Collector and Item the test collection tree.
9386
Collector subclasses have children, Items are terminal nodes."""
9487

@@ -151,22 +144,13 @@ def __init__(
151144
self._chain = self.listchain()
152145

153146
@classmethod
154-
def from_parent(cls, parent: "Node", **kw):
155-
"""
156-
Public Constructor for Nodes
157-
158-
This indirection got introduced in order to enable removing
159-
the fragile logic from the node constructors.
160-
161-
Subclasses can use ``super().from_parent(...)`` when overriding the construction
162-
163-
:param parent: the parent node of this test Node
164-
"""
147+
def from_parent(cls: "Type[_N]", parent: "Node", **kw) -> "_N":
148+
"""Public constructor for Nodes (for compatibility with pytest only)."""
165149
if "config" in kw:
166150
raise TypeError("config is not a valid argument for from_parent")
167151
if "session" in kw:
168152
raise TypeError("session is not a valid argument for from_parent")
169-
return cls._create(parent=parent, **kw)
153+
return cls(parent=parent, **kw)
170154

171155
@property
172156
def ihook(self):
@@ -471,13 +455,6 @@ def __init__(
471455

472456
self._norecursepatterns = self.config.getini("norecursedirs")
473457

474-
@classmethod
475-
def from_parent(cls, parent, *, fspath):
476-
"""
477-
The public constructor
478-
"""
479-
return super().from_parent(parent=parent, fspath=fspath)
480-
481458
def _gethookproxy(self, fspath: py.path.local):
482459
# check if we have the common case of running
483460
# hooks with all conftest.py files

src/_pytest/pytester/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -906,7 +906,7 @@ def getnode(self, config, arg):
906906
:param arg: a :py:class:`py.path.local` instance of the file
907907
908908
"""
909-
session = Session.from_config(config)
909+
session = Session(config)
910910
assert "::" not in str(arg)
911911
p = py.path.local(arg)
912912
config.hook.pytest_sessionstart(session=session)
@@ -924,7 +924,7 @@ def getpathnode(self, path):
924924
925925
"""
926926
config = self.parseconfigure(path)
927-
session = Session.from_config(config)
927+
session = Session(config)
928928
x = session.fspath.bestrelpath(path)
929929
config.hook.pytest_sessionstart(session=session)
930930
res = session.perform_collect([x], genitems=False)[0]

src/_pytest/python.py

Lines changed: 10 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -213,8 +213,8 @@ def path_matches_patterns(path, patterns):
213213

214214
def pytest_pycollect_makemodule(path, parent):
215215
if path.basename == "__init__.py":
216-
return Package.from_parent(parent, fspath=path)
217-
return Module.from_parent(parent, fspath=path)
216+
return Package(path, parent)
217+
return Module(path, parent)
218218

219219

220220
@hookimpl(hookwrapper=True)
@@ -226,7 +226,7 @@ def pytest_pycollect_makeitem(collector, name, obj):
226226
# nothing was collected elsewhere, let's do it here
227227
if safe_isclass(obj):
228228
if collector.istestclass(obj, name):
229-
outcome.force_result(Class.from_parent(collector, name=name, obj=obj))
229+
outcome.force_result(Class(name, parent=collector))
230230
elif collector.istestfunction(obj, name):
231231
# mock seems to store unbound methods (issue473), normalize it
232232
obj = getattr(obj, "__func__", obj)
@@ -245,7 +245,7 @@ def pytest_pycollect_makeitem(collector, name, obj):
245245
)
246246
elif getattr(obj, "__test__", True):
247247
if is_generator(obj):
248-
res = Function.from_parent(collector, name=name)
248+
res = Function(name, parent=collector)
249249
reason = "yield tests were removed in pytest 4.0 - {name} will be ignored".format(
250250
name=name
251251
)
@@ -415,7 +415,7 @@ def _genfunctions(self, name, funcobj):
415415
cls = clscol and clscol.obj or None
416416
fm = self.session._fixturemanager
417417

418-
definition = FunctionDefinition.from_parent(self, name=name, callobj=funcobj)
418+
definition = FunctionDefinition(name=name, parent=self, callobj=funcobj)
419419
fixtureinfo = definition._fixtureinfo
420420

421421
metafunc = Metafunc(
@@ -430,7 +430,7 @@ def _genfunctions(self, name, funcobj):
430430
self.ihook.pytest_generate_tests.call_extra(methods, dict(metafunc=metafunc))
431431

432432
if not metafunc._calls:
433-
yield Function.from_parent(self, name=name, fixtureinfo=fixtureinfo)
433+
yield Function(name, parent=self, fixtureinfo=fixtureinfo)
434434
else:
435435
# add funcargs() as fixturedefs to fixtureinfo.arg2fixturedefs
436436
fixtures.add_funcarg_pseudo_fixture_def(self, metafunc, fm)
@@ -442,9 +442,9 @@ def _genfunctions(self, name, funcobj):
442442

443443
for callspec in metafunc._calls:
444444
subname = "{}[{}]".format(name, callspec.id)
445-
yield Function.from_parent(
446-
self,
445+
yield Function(
447446
name=subname,
447+
parent=self,
448448
callspec=callspec,
449449
callobj=funcobj,
450450
fixtureinfo=fixtureinfo,
@@ -615,7 +615,7 @@ def collect(self):
615615
if init_module.check(file=1) and path_matches_patterns(
616616
init_module, self.config.getini("python_files")
617617
):
618-
yield Module.from_parent(self, fspath=init_module)
618+
yield Module(init_module, self)
619619
pkg_prefixes = set()
620620
for path in this_path.visit(rec=self._recurse, bf=True, sort=True):
621621
# We will visit our own __init__.py file, in which case we skip it.
@@ -666,13 +666,6 @@ def _get_first_non_fixture_func(obj, names):
666666
class Class(PyCollector):
667667
""" Collector for test methods. """
668668

669-
@classmethod
670-
def from_parent(cls, parent, *, name, obj=None):
671-
"""
672-
The public constructor
673-
"""
674-
return super().from_parent(name=name, parent=parent)
675-
676669
def collect(self):
677670
if not safe_getattr(self.obj, "__test__", True):
678671
return []
@@ -698,7 +691,7 @@ def collect(self):
698691
self._inject_setup_class_fixture()
699692
self._inject_setup_method_fixture()
700693

701-
return [Instance.from_parent(self, name="()")]
694+
return [Instance(name="()", parent=self)]
702695

703696
def _inject_setup_class_fixture(self):
704697
"""Injects a hidden autouse, class scoped fixture into the collected class object
@@ -1456,13 +1449,6 @@ def __init__(
14561449
#: .. versionadded:: 3.0
14571450
self.originalname = originalname
14581451

1459-
@classmethod
1460-
def from_parent(cls, parent, **kw): # todo: determine sound type limitations
1461-
"""
1462-
The public constructor
1463-
"""
1464-
return super().from_parent(parent=parent, **kw)
1465-
14661452
def _initrequest(self):
14671453
self.funcargs = {}
14681454
self._request = fixtures.FixtureRequest(self)

src/_pytest/unittest.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ def pytest_pycollect_makeitem(collector, name, obj):
2424
except Exception:
2525
return
2626
# yes, so let's collect it
27-
return UnitTestCase.from_parent(collector, name=name, obj=obj)
27+
return UnitTestCase(name, parent=collector)
2828

2929

3030
class UnitTestCase(Class):
@@ -52,16 +52,15 @@ def collect(self):
5252
if not getattr(x, "__test__", True):
5353
continue
5454
funcobj = getimfunc(x)
55-
yield TestCaseFunction.from_parent(self, name=name, callobj=funcobj)
55+
yield TestCaseFunction(name, parent=self, callobj=funcobj)
5656
foundsomething = True
5757

5858
if not foundsomething:
5959
runtest = getattr(self.obj, "runTest", None)
6060
if runtest is not None:
6161
ut = sys.modules.get("twisted.trial.unittest", None)
6262
if ut is None or runtest != ut.TestCase.runTest:
63-
# TODO: callobj consistency
64-
yield TestCaseFunction.from_parent(self, name="runTest")
63+
yield TestCaseFunction("runTest", parent=self)
6564

6665
def _inject_setup_teardown_fixtures(self, cls):
6766
"""Injects a hidden auto-use fixture to invoke setUpClass/setup_method and corresponding

testing/deprecated_test.py

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
import pytest
44
from _pytest import deprecated
5-
from _pytest import nodes
65

76

87
@pytest.mark.parametrize(
@@ -103,20 +102,6 @@ def test_foo():
103102
result.stdout.fnmatch_lines([warning_msg])
104103

105104

106-
def test_node_direct_ctor_warning():
107-
class MockConfig:
108-
pass
109-
110-
ms = MockConfig()
111-
with pytest.warns(
112-
DeprecationWarning,
113-
match="direct construction of .* has been deprecated, please use .*.from_parent",
114-
) as w:
115-
nodes.Node(name="test", config=ms, session=ms, nodeid="None")
116-
assert w[0].lineno == inspect.currentframe().f_lineno - 1
117-
assert w[0].filename == __file__
118-
119-
120105
def assert_no_print_logs(testdir, args):
121106
result = testdir.runpytest(*args)
122107
result.stdout.fnmatch_lines(

0 commit comments

Comments
 (0)