From 73e06373dc67055bf83d9a1f6cf3242bc90a68c3 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Mon, 17 Aug 2020 17:34:28 -0300 Subject: [PATCH] Hard failure when constructing Node subclasses --- changelog/5585.breaking.rst | 2 ++ doc/en/deprecations.rst | 57 ++++++++++++++++++------------------- src/_pytest/deprecated.py | 7 ----- src/_pytest/nodes.py | 10 +++++-- testing/deprecated_test.py | 16 ----------- 5 files changed, 36 insertions(+), 56 deletions(-) diff --git a/changelog/5585.breaking.rst b/changelog/5585.breaking.rst index 655c4a42e..bbf133a38 100644 --- a/changelog/5585.breaking.rst +++ b/changelog/5585.breaking.rst @@ -4,3 +4,5 @@ removed: * The ``funcargnames`` read-only property of ``FixtureRequest``, ``Metafunc``, and ``Function`` classes. Use ``fixturenames`` attribute. * ``@pytest.fixture`` no longer supports positional arguments, pass all arguments by keyword instead. + +* Direct construction of ``Node`` subclasses now raise an error, use ``from_parent`` instead. diff --git a/doc/en/deprecations.rst b/doc/en/deprecations.rst index 886bfd7fc..72a0e5b93 100644 --- a/doc/en/deprecations.rst +++ b/doc/en/deprecations.rst @@ -43,7 +43,6 @@ it, use `function._request._fillfixtures()` instead, though note this is not a public API and may break in the future. - ``--no-print-logs`` command-line option ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -58,35 +57,6 @@ A ``--show-capture`` command-line option was added in ``pytest 3.5.0`` which all display captured output when tests fail: ``no``, ``stdout``, ``stderr``, ``log`` or ``all`` (the default). - -Node Construction changed to ``Node.from_parent`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. deprecated:: 5.4 - -The construction of nodes now should use the named constructor ``from_parent``. -This limitation in api surface intends to enable better/simpler refactoring of the collection tree. - -This means that instead of :code:`MyItem(name="foo", parent=collector, obj=42)` -one now has to invoke :code:`MyItem.from_parent(collector, name="foo")`. - -Plugins that wish to support older versions of pytest and suppress the warning can use -`hasattr` to check if `from_parent` exists in that version: - -.. code-block:: python - - def pytest_pycollect_makeitem(collector, name, obj): - if hasattr(MyItem, "from_parent"): - item = MyItem.from_parent(collector, name="foo") - item.obj = 42 - return item - else: - return MyItem(name="foo", parent=collector, obj=42) - -Note that ``from_parent`` should only be called with keyword arguments for the parameters. - - - ``junit_family`` default value change to "xunit2" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -162,6 +132,33 @@ Removed Features As stated in our :ref:`backwards-compatibility` policy, deprecated features are removed only in major releases after an appropriate period of deprecation has passed. +Node Construction changed to ``Node.from_parent`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. deprecated:: 6.0 + +The construction of nodes now should use the named constructor ``from_parent``. +This limitation in api surface intends to enable better/simpler refactoring of the collection tree. + +This means that instead of :code:`MyItem(name="foo", parent=collector, obj=42)` +one now has to invoke :code:`MyItem.from_parent(collector, name="foo")`. + +Plugins that wish to support older versions of pytest and suppress the warning can use +`hasattr` to check if `from_parent` exists in that version: + +.. code-block:: python + + def pytest_pycollect_makeitem(collector, name, obj): + if hasattr(MyItem, "from_parent"): + item = MyItem.from_parent(collector, name="foo") + item.obj = 42 + return item + else: + return MyItem(name="foo", parent=collector, obj=42) + +Note that ``from_parent`` should only be called with keyword arguments for the parameters. + + ``pytest.fixture`` arguments are keyword only ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/src/_pytest/deprecated.py b/src/_pytest/deprecated.py index 2f3c80251..401cce80d 100644 --- a/src/_pytest/deprecated.py +++ b/src/_pytest/deprecated.py @@ -30,13 +30,6 @@ RESULT_LOG = PytestDeprecationWarning( "See https://docs.pytest.org/en/stable/deprecations.html#result-log-result-log for more information." ) -NODE_USE_FROM_PARENT = UnformattedWarning( - PytestDeprecationWarning, - "Direct construction of {name} has been deprecated, please use {name}.from_parent.\n" - "See " - "https://docs.pytest.org/en/stable/deprecations.html#node-construction-changed-to-node-from-parent" - " for more details.", -) JUNIT_XML_DEFAULT_FAMILY = PytestDeprecationWarning( "The 'junit_family' default value will change to 'xunit2' in pytest 6.0. See:\n" diff --git a/src/_pytest/nodes.py b/src/_pytest/nodes.py index 79e091443..8e3e86a50 100644 --- a/src/_pytest/nodes.py +++ b/src/_pytest/nodes.py @@ -26,7 +26,6 @@ from _pytest.compat import TYPE_CHECKING from _pytest.config import Config from _pytest.config import ConftestImportFailure from _pytest.deprecated import FSCOLLECTOR_GETHOOKPROXY_ISINITPATH -from _pytest.deprecated import NODE_USE_FROM_PARENT from _pytest.fixtures import FixtureDef from _pytest.fixtures import FixtureLookupError from _pytest.mark.structures import Mark @@ -97,8 +96,13 @@ _NodeType = TypeVar("_NodeType", bound="Node") class NodeMeta(type): def __call__(self, *k, **kw): - warnings.warn(NODE_USE_FROM_PARENT.format(name=self.__name__), stacklevel=2) - return super().__call__(*k, **kw) + msg = ( + "Direct construction of {name} has been deprecated, please use {name}.from_parent.\n" + "See " + "https://docs.pytest.org/en/stable/deprecations.html#node-construction-changed-to-node-from-parent" + " for more details." + ).format(name=self.__name__) + fail(msg, pytrace=False) def _create(self, *k, **kw): return super().__call__(*k, **kw) diff --git a/testing/deprecated_test.py b/testing/deprecated_test.py index 9507d9902..aab7b7b36 100644 --- a/testing/deprecated_test.py +++ b/testing/deprecated_test.py @@ -1,11 +1,9 @@ import copy -import inspect import warnings from unittest import mock import pytest from _pytest import deprecated -from _pytest import nodes from _pytest.config import Config from _pytest.pytester import Testdir @@ -106,20 +104,6 @@ def test_warn_about_imminent_junit_family_default_change(testdir, junit_family): result.stdout.fnmatch_lines([warning_msg]) -def test_node_direct_ctor_warning() -> None: - class MockConfig: - pass - - ms = MockConfig() - with pytest.warns( - DeprecationWarning, - match="Direct construction of .* has been deprecated, please use .*.from_parent.*", - ) as w: - nodes.Node(name="test", config=ms, session=ms, nodeid="None") # type: ignore - assert w[0].lineno == inspect.currentframe().f_lineno - 1 # type: ignore - assert w[0].filename == __file__ - - @pytest.mark.skip(reason="should be reintroduced in 6.1: #7361") def test_fillfuncargs_is_deprecated() -> None: with pytest.warns(